(back to project page)

MissileCommand Disassembly

                   ********************************************************************************
                   * Missile Command (rev 3)                                                      *
                   * By Dave Theurer and Rich Adam                                                *
                   * Copyright 1980 Atari, Inc.                                                   *
                   ********************************************************************************
                   * Disassembly by Andy McFadden, using 6502bench SourceGen v1.8.                *
                   * Last updated 2021/11/30.                                                     *
                   *                                                                              *
                   * Thanks to:                                                                   *
                   *   + The various MAME contributors.                                           *
                   *                                                                              *
                   * POKEY register descriptions from https://en.wikipedia.org/wiki/POKEY .       *
                   *                                                                              *
                   * The binary used is a concatenation of the six 2KB ROMs that are addressable  *
                   * by the 6502.  The differences from the rev 2 ROM set are small in scope,     *
                   * affecting only a couple of routines, but fix a pair of bugs that greatly     *
                   * impact world-record score attempts (see the rev 2 diffs page).               *
                   *                                                                              *
                   * Terminology: I generally refer to incoming missiles as "ICBMs" (Inter-       *
                   * Continental Ballistic Missile), missiles fired by the player as "ABMs"       *
                   * (Anti-Ballistic Missiles), missiles that split as "MIRVs" (Multiple          *
                   * Independent Rentry Vehicle), and planes and satellites as "fliers".  The     *
                   * terms aren't necessarily accurate, but they're short and avoid ambiguity.    *
                   *                                                                              *
                   * A few days after I started this, the original sources were posted to         *
                   * https://github.com/historicalsource/missile-command .  I was unaware of them *
                   * until after this was completed.  I've updated the Notes in the project to    *
                   * make it easier to match the diassembly to the sources (e.g. "A35820.1A" is   *
                   * the name of a source file).                                                  *
                   ********************************************************************************
                   * Memory map in a nutshell:                                                    *
                   *   $0000-01ff: RAM (zero page, stack; most game state is here)                *
                   *   $0200-05ff: video RAM - 3rd color bit for last 32 lines                    *
                   *   $0600-063f: RAM (game state)                                               *
                   *   $0640-3fff: video RAM - 2-bit color values                                 *
                   *   $4000-4fff: I/O ports                                                      *
                   *   $5000-7fff: game ROM                                                       *
                   *                                                                              *
                   * The video output has a resolution of 256x231.  Most of the screen uses 2     *
                   * bits per pixel to index into a color palette; packed tightly into bytes, we  *
                   * need 256x231/4 = $39c0 bytes (= $4000-$640).  Bytes are arranged linearly,   *
                   * but the bits are split across nibbles, and the low bit of the palette index  *
                   * is zero.  If the bits in the first byte of the line are %ABCDabcd, then the  *
                   * color index for the pixel at X=0 is %00000Dd0, X=1 is %00000Cc0, etc.        *
                   *                                                                              *
                   * The bottom 32 lines, which includes the area with the cities, has a 3rd      *
                   * color bit that is added as the low bit of the color index.  This occupies    *
                   * 1024 bytes at $0200.  Adjacent lines are still separated by 64 bytes in      *
                   * memory, but the first 16 lines are stored in even bytes and the second 16 in *
                   * odd bytes.  For example, the bits for the leftmost 16 pixels of line 199 are *
                   * at $0200 and $0202, while those for line 215 are at $0201 and $0203.         *
                   *                                                                              *
                   * If you access memory with "LDA ($ZZ,X)" or "STA ($ZZ,X)", the address and    *
                   * data is remapped to a layout with one byte per pixel (look for "MADSEL" in   *
                   * the schematics).  Color indices are stored as %XYZ00000, where XY are the    *
                   * 2bpp color values, and Z is the third bit (always zero until you reach the   *
                   * 199th line at $e000).  For example, if you want to set the color at X=128    *
                   * Y=103 to %110, you can either set $2020=$11 with "STA addr", or set          *
                   * $8080=$c0 with "STA (ZP,X)".                                                 *
                   *                                                                              *
                   * I/O port definitions (from MAME driver and manual):                          *
                   *                                                                              *
                   * IN0 ($4800 if CTRLD is 0): CCC12LMR                                          *
                   *   CCC=coin slots (R/C/L)                                                     *
                   *   1=player 1 start                                                           *
                   *   2=player 2 start                                                           *
                   *   L=2nd player left fire (cocktail cabinet)                                  *
                   *   M=2nd player center fire                                                   *
                   *   R=2nd player right fire                                                    *
                   *                                                                              *
                   * IN0 ($4800 if CTRLD is 1): VVVVHHHH                                          *
                   *   HHHH: horizontal trackball displacement                                    *
                   *   VVVV: vertical trackball displacement                                      *
                   *                                                                              *
                   * OUT0 ($4800): UFCCC21D                                                       *
                   *   U=unused?                                                                  *
                   *   F=screen flip (1=normal orientation)                                       *
                   *   CCC=coin counter (L/C/R)                                                   *
                   *   2=2-player start LED (0=lit)                                               *
                   *   1=1-player start LED (0=lit)                                               *
                   *   D=CTRLD (0=read switches, 1=read trackball)                                *
                   *                                                                              *
                   * IN1 ($4900): VTSHVLMR                                                        *
                   *   V=VBLANK read (1=in VBLANK)                                                *
                   *   T=self test switch (0=do test)                                             *
                   *   S=slam switch (0=slammed)                                                  *
                   *   H=horiz trackball direction input (?)                                      *
                   *   V=vert trackball direction input (?)                                       *
                   *   L=1st player left fire (0=pressed)                                         *
                   *   M=1st player center fire                                                   *
                   *   R=1st player right fire                                                    *
                   *                                                                              *
                   * Switch R8 ($4808, POKEY_ALLPOT): KBBBTQCC                                    *
                   *   K=cocktail cabinet (1=enable flip)                                         *
                   *   BBB=bonus city every N points                                              *
                   *   T=large trak-ball input (off for upright games)                            *
                   *   Q=bonus credit for 4 successive quarters                                   *
                   *   CC=starting cities (4-7)                                                   *
                   *                                                                              *
                   * Switch R10 ($4a00): ULLCCCPP                                                 *
                   *   U=unused                                                                   *
                   *   LL=language (00=en, 01=fr, 10=de, 11=es)                                   *
                   *   CCC=coin mechanism config                                                  *
                   *   PP=coins/play                                                              *
                   *                                                                              *
                   * Color palette ($4b00-4b07): UUUURGBU x8                                      *
                   *   UUUU=unused                                                                *
                   *   RGB=color (0=on, 1=off, so 111 is black and 001 is yellow)                 *
                   *   U=unused                                                                   *
                   ********************************************************************************
                   * The missile/smartbomb tracking tables have 16 entries.  The first 8 are for  *
                   * ABMs, the last 8 are for ICBMs or smartbombs.  The current-position table is *
                   * followed by the flier's position, so some of the code treats the flier like  *
                   * a 9th ICBM.  Most code that deals with missiles takes a "slot index" (0-15), *
                   * but some of the ICBM code takes an "ICBM index" instead (0-7).               *
                   ********************************************************************************
                   FN_INIT_GAME    .eq     $00    {const}
                   SFX_SILO_LOW    .eq     $01    {const}    ;sound effect: silo low warning
                   FN_PREP_WAVE    .eq     $02    {const}
                   SFX_EXPLOSION   .eq     $02    {const}    ;sound effect: explosion
                   FN_PLAY_GAME    .eq     $04    {const}
                   SFX_ABM_LAUNCH  .eq     $04    {const}    ;sound effect: ABM launch
                   FN_GAME_OVER    .eq     $06    {const}
                   FN_END_WAVE     .eq     $08    {const}
                   SFX_BONUS_PTS   .eq     $08    {const}    ;sound effect: bonus points awarded
                   FN_ABM_BONUS    .eq     $0a    {const}
                   FN_CITY_BONUS_CHK .eq   $0c    {const}
                   FN_CITY_BONUS_PTS .eq   $0e    {const}
                   FN_WAVE_DONE_UPD .eq    $10    {const}
                   SFX_START_WAVE  .eq     $10    {const}    ;sound effect: wave starting
                   FN_THE_END      .eq     $12    {const}
                   FN_SHOW_TITLE   .eq     $14    {const}
                   FN_TITLE_EXPLS  .eq     $16    {const}
                   FN_HS_CHECK     .eq     $18    {const}
                   FN_ENTER_INITIALS .eq   $1a    {const}
                   FN_SHOW_HS      .eq     $1c    {const}
                   FN_HALF_CREDIT  .eq     $1e    {const}
                   FN_WAIT_FULL_CREDIT .eq $20    {const}
                   SFX_GAME_OVER   .eq     $20    {const}    ;sound effect: game over
                   FN_PAUSE        .eq     $22    {const}
                   SFX_BONUS_CITY  .eq     $40    {const}    ;sound effect: bonus city used
                   SFX_CANT_FIRE   .eq     $80    {const}    ;sound effect: can't fire ABM
                   tmp_02          .eq     $02               ;general use
                   gfx_ptr         .eq     $06    {addr/2}   ;pointer used for graphics
                   preserve_bg_flag .eq    $08               ;if hi bit set, preserve bg when drawing bitmap
                   last_cross_xc   .eq     $09               ;previously-drawn crosshairs X-coord
                   last_cross_yc   .eq     $0a               ;previously-drawn crosshairs Y-coord
                   draw_fg_color   .eq     $0b               ;foreground color for bitmap drawing routines
                   draw_bg_color   .eq     $0c               ;background color for bitmap drawing routines
                   cross_back_store .eq    $0f    {addr/14}  ;backing store for crosshair drawing
                   txsc_out_idx    .eq     $25               ;text scroll: index into text-scroll message index table
                   txsc_msg_offset .eq     $26               ;text scroll: offset within current message
                   txsc_char_delay .eq     $27               ;text scroll: frames until next char is output
                   txsc_play_cost  .eq     $28               ;text scroll: play cost message
                   txsc_credits    .eq     $29               ;text scroll: last shown credit count
                   msg_base_ptr    .eq     $2a    {addr/2}   ;pointer used when drawing messages
                   hscore_initials .eq     $2c    {addr/24}  ;8 scores, 3 ASCII chars each
                   hscore_scores   .eq     $44    {addr/24}  ;8 scores, 3 BCD bytes each
                   coin_timer1?    .eq     $5c    {addr/3}   ;coin slot timers, 1 per slot
                   slam_tone_ctr1  .eq     $5f               ;set to $f0 on tilt (in IRQ)
                   coin_timer2?    .eq     $60    {addr/3}   ;coin slot timers, 1 per slot
                   coin_drop_ctr   .eq     $63    {addr/3}   ;left, center, right
                   credit_count    .eq     $66               ;number of credits purchased (0-40)
                   coins_inserted  .eq     $67               ;coins not yet spent on credits
                   recent_coins    .eq     $68               ;coins inserted since last game, for bonus-for-4
                   last_sound_started .eq  $69               ;last sound started, used for bonus city
                   audio_indices   .eq     $6a    {addr/8}   ;index into sfx table, nonzero if active
                   audio_values    .eq     $72    {addr/8}   ;values copied to POKEY $4000-4007
                   audio_dur_ctr   .eq     $7a    {addr/8}   ;duration counter, updated during IRQ
                   audio_rep_ctr   .eq     $82    {addr/8}   ;repetition counter, updated during IRQ
                   engine_sfx_flags .eq    $8a               ;bits set for fliers and smartbombs
                   engine_sfx_freq .eq     $8b               ;current frequency for engine sound
                   num_live_icbms  .eq     $8c               ;number of active ICBMs on screen
                   wave_icbm_count .eq     $8d               ;#of ICBMs appearing in this wave
                   num_explosions  .eq     $8e               ;number of active explosions
                   num_expl_abms   .eq     $8f               ;number of exploding ABMs
                   num_expl_icbms  .eq     $90               ;number of exploding ICBMs
                   func_index      .eq     $91               ;top-level function index (0-34, by 2)
                   next_func_index .eq     $92               ;func to switch to after delay
                   play_mode_flag  .eq     $93               ;bool 00/ff: is a game in progress?
                   trk_h_delta     .eq     $94               ;trackball horizontal movement
                   trk_v_delta     .eq     $95               ;trackball vertical movement
                   next_expl_slot  .eq     $96               ;next explosion slot
                   slot_index      .eq     $97               ;used to pass slot index for ABMs and smart bombs
                   tmp_98          .eq     $98               ;general use
                   tmp_99          .eq     $99               ;general use
                   exec_flag       .eq     $9f               ;incremented by IRQ, zeroed by main loop
                   abms_left       .eq     $a0    {addr/3}   ;#of ABMs left at each silo (0-10)
                   cross_frac      .eq     $a3    {addr/2}   ;fractional part of crosshair X/Y posn
                   cross_xc        .eq     $a5               ;current crosshair X-coord
                   cross_yc        .eq     $a6               ;current crosshair Y-coord, 0 if hidden
                   wave_num        .eq     $a7               ;current wave number, starts at 1
                   num_players     .eq     $ae               ;number of players minus 1 (0 or 1)
                   frame4_delay_ctr .eq    $af               ;counts down frames before continuing in func $22
                   icbm_move_ctr   .eq     $b2    {addr/2}   ;ICBM movement counter, as 8.8 value
                   exp_seq_index   .eq     $b4               ;explosion sequence index (0-4)
                   icbm_move_delay .eq     $b7    {addr/2}   ;delay between ICBM updates, as 8.8 value
                   cur_plyr_num    .eq     $b9               ;current player number (0/1)
                   base_score_bcd  .eq     $ba    {addr/2}   ;$25 * current score multiplier
                   active_abm_count .eq    $bc               ;number of ABMs in flight?
                   incoming_min_y  .eq     $bd               ;Y coord of lowest bomb/icbm (unused?)
                   incoming_max_y  .eq     $be               ;Y-coord of highest bomb/missile
                   full_speed_flag .eq     $bf               ;bool 00/ff: move everything at full speed?
                   num_cities      .eq     $c0    {addr/2}   ;total cities, both live and bonus (per plyr)
                   mirv_slot       .eq     $c2               ;slot number of splittable ICBM
                   bonus_award_sc  .eq     $c3    {addr/2}   ;score when last bonus city awarded (per plyr)
                   live_city_mask  .eq     $c5    {addr/2}   ;bits for live cities (start hi) (per plyr)
                   flier_move_ctr  .eq     $c7               ;flier movement interval counter (1 or 2)
                   flier_move_dir  .eq     $c8               ;flier movement dir: $ff=left, $00=right
                   flier_fire_ctr  .eq     $c9               ;counts up time until flier can fire
                   game_frame_ctr  .eq     $ca               ;incr once per game frame, <= 60Hz (non-IRQ)
                   city_targ_flags .eq     $cb               ;ICBM target flags for cities
                   silo_targ_flags .eq     $cc               ;ICBM target flags for silos
                   live_silo_mask  .eq     $cd               ;bits for live silos, %LCR00000
                   score_color     .eq     $ce               ;color to use for scores (%PPP00000)
                   cities_destroyed .eq    $cf               ;number of cities destroyed in current wave
                   sbomb_dir       .eq     $d0               ;direction smartbomb wants to move (0-15)
                   bcd3_acc        .eq     $d1    {addr/3}   ;3-byte BCD accumulator
                   score_dirty_flag .eq    $d4               ;bool 00/ff: scores need to be redrawn?
                   sbomb_prox      .eq     $d5               ;bit map of proximal explosions
                   flier_type      .eq     $d6               ;active flier type: 0=satellite, 1=plane
                   draw_1up_flag   .eq     $d7               ;draw phase of the flashing "1UP" glyph (0=draw)
                   indic_phase_counter .eq $d8               ;"1UP" display counter, decr every VBLANK
                   smartbomb_mask  .eq     $d9               ;indicates which of 8 ICBM slots hold bombs
                   num_live_bombs  .eq     $da               ;number of active smartbombs on screen
                   wave_sbomb_count .eq    $db               ;#of smartbombs appearing in current wave
                   incoming_ctr    .eq     $dc               ;ICBM slot number, during processing
                   score_mult      .eq     $dd               ;score multiplier (1-6)
                   atk_create_ctr  .eq     $de               ;number of attacks to create this frame
                   next_player     .eq     $df               ;next player number (0/1)
                   slam_tone_ctr2  .eq     $e0               ;set to $f0 on tilt (in main code)
                   flier_fire_delay .eq    $e1               ;#of frames before flier can fire
                   flier_charge_int .eq    $e2               ;#of frames to "recharge" flier
                   flier_charge_ctr .eq    $e3               ;counts up, flier ready when exceeds $e2 val
                   color_palette   .eq     $e4    {addr/8}   ;colors to copy to display H/W on next IRQ
                   color_cyc_mask  .eq     $ec               ;mask of palette entries to cycle
                   in01_current    .eq     $ed    {addr/2}   ;current state of IN0/IN1
                   in01_prev       .eq     $ef    {addr/2}   ;previous state of IN0/IN1
                   in0_value       .eq     $f1               ;debounced IN0 value (switches)
                   in1_value       .eq     $f2               ;debounced IN1 value
                   r10_irq_mod     .eq     $f3               ;R10 at last IRQ, bit 1 inverted
                   r8_irq_inv      .eq     $f4               ;R8 at last IRQ, all bits inverted
                   out0_bits       .eq     $f5               ;bits last written to OUT0
                   in0_trkball1    .eq     $f6               ;trackball input (used by IRQ)
                   in0_trk_prev    .eq     $f7               ;previous trackball read (used by IRQ)
                   in0_trkball2    .eq     $f8               ;trackball input (used by IRQ)
                   prev_plyr_in01  .eq     $f9               ;set by IRQ to IN0/IN1 for active player
                   fire_buttons    .eq     $fa               ;pressed fire buttons, %00000LCR (set by IRQ)
                   irq_counter     .eq     $fb               ;counts up every IRQ, used as time multiplier
                   cocktail_flip   .eq     $fc               ;$00=upright, $80=ckt plyr 1, $ff=ckt plyr 2
                   STACK           .eq     $0100  {addr/256} ;6502 stack
                   stack_plus4     .eq     $0104             ;stack offset
                   stack_plus6     .eq     $0106             ;stack offset
                   abm_cur_xfrac   .eq     $0118  {addr/8}   ;current ABM X coord, fractional part
                   icbm_cur_xfrac  .eq     $0120  {addr/8}   ;(must follow ABM array)
                   abm_cur_x       .eq     $0128  {addr/8}   ;current ABM X coord, int part
                   icbm_cur_x      .eq     $0130  {addr/8}   ;(must follow ABM array)
                   plane_cur_x     .eq     $0138             ;flier X coord (follows ICBM)
                   expl_xpos       .eq     $0139  {addr/20}  ;X coordinates of explosions
                   abm_cur_yfrac   .eq     $014d  {addr/8}   ;current ABM Y coord, fractional part
                   icbm_cur_yfrac  .eq     $0155  {addr/8}   ;(must follow ABM array)
                   abm_cur_y       .eq     $015d  {addr/8}   ;current ABM Y coord, int part
                   icbm_cur_y      .eq     $0165  {addr/8}   ;(must follow ABM array)
                   flier_cur_y     .eq     $016d             ;flier Y coord, or 0 if inactive (follows ICBM)
                   expl_ypos       .eq     $016e  {addr/20}  ;Y coordinates of explosions
                   abm_start_x_arr .eq     $0182  {addr/8}   ;initial ABM X coord
                   icbm_start_x_arr .eq    $018a  {addr/8}   ;(must follow ABM array)
                   abm_start_y_arr .eq     $0192  {addr/8}   ;initial ABM Y coord
                   icbm_start_y_arr .eq    $019a  {addr/8}   ;(must follow ABM array)
                   abm_targ_x_arr  .eq     $01a2  {addr/8}   ;X coords of ABM targets
                   icbm_targ_x_arr .eq     $01aa  {addr/8}   ;(must follow ABM array)
                   abm_targ_y_arr  .eq     $01b2  {addr/8}   ;Y coords of ABM targets
                   icbm_targ_y_arr .eq     $01ba  {addr/8}   ;(must follow ABM array)
                   expl_radius_idx_arr .eq $01c2  {addr/20}  ;index into expl radius table ($00-1b)
                   cur_score_lo    .eq     $01d6  {addr/2}   ;current score, BCD low part (2 plyr)
                   cur_score_md    .eq     $01d8  {addr/2}   ;current score, BCD mid part (2 plyr)
                   cur_score_hi    .eq     $01da  {addr/2}   ;current score, BCD hi part (2 plyr)
                   VIDEO_RAM_3     .eq     $0200  {addr/1024} ;3rd color bit region
                   msl_inc_xfrac   .eq     $0600  {addr/16}  ;missile X move increment, fractional part
                   msl_inc_xint    .eq     $0610  {addr/16}  ;missile X move increment, int part
                   msl_inc_yfrac   .eq     $0620  {addr/16}  ;missile Y move increment, fractional part
                   msl_inc_yint    .eq     $0630  {addr/16}  ;missile Y move increment, int part
                   VIDEO_RAM_2     .eq     $0640  {addr/14784} ;2bpp video RAM
                   POKEY_AUDF1     .eq     $4000             ;W audio channel 1 frequency
                   POKEY_AUDC1     .eq     $4001             ;W audio channel 1 control
                   POKEY_AUDF2     .eq     $4002             ;W audio channel 2 frequency
                   POKEY_AUDC2     .eq     $4003             ;W audio channel 2 control
                   POKEY_AUDC3     .eq     $4005             ;W audio channel 3 control
                   POKEY_AUDF4     .eq     $4006             ;W audio channel 4 frequency
                   POKEY_AUDC4     .eq     $4007             ;W audio channel 4 control
                   POKEY_AUDCTL    .eq     $4008             ;W audio control
                   SWITCH_R8       .eq     $4008             ;R switches at R8 (IN3 / POKEY_ALLPOT)
                   POKEY_RANDOM    .eq     $400a             ;R random number
                   POKEY_POTGO     .eq     $400b             ;W start POT scan sequence
                   POKEY_SKCTL     .eq     $400f             ;W serial port control
                   IN0             .eq     $4800             ;R coin, start, player 2 fire; CTRLD trackball
                   OUT0            .eq     $4800             ;W flip, LEDs, CTRLD
                   IN1             .eq     $4900             ;R buttons, tilt, service
                   SWITCH_R10      .eq     $4a00             ;R pricing opt switches at R10 (IN2)
                   COLOR_PAL       .eq     $4b00  {addr/8}   ;W color palette
                   WATCHDOG        .eq     $4c00             ;W watchdog timer reset
                   INT_ACK         .eq     $4d00             ;W interrupt acknowledge

                                   .addrs  $5000
5000: 29                           .dd1    $29               ;checksum byte

                   ********************************************************************************
                   *                                                                              *
                   * Main code start point.  Only executes after a reset.                         *
                   *                                                                              *
                   ********************************************************************************
5001: 58           START           cli                       ;enable interrupts
5002: 20 e1 74                     jsr     InitDefScores     ;initialize high score list to defaults
5005: 20 2f 67                     jsr     InitLangAndFlip   ;init localization and screen flip
5008: 20 0c 7b                     jsr     InitPokey         ;init sound generation
500b: a9 14                        lda     #FN_SHOW_TITLE    ;start by showing the title screen
500d: 85 91                        sta     func_index
                   ; 
                   ; Main code loop.  We spin until the IRQ tells us to go, which it does once per
                   ; frame (~60Hz).
                   ; 
                   ; The game uses a state machine to manage the higher-level activity, e.g.
                   ; showing the title sequence vs. showing high scores vs. playing the game.  The
                   ; code for each state will update the function number (in $91) when it's time to
                   ; move on.
                   ; 
                   ; The main loop executes the current function, calls a routine to track any
                   ; quarters that have dropped, and then loops around to wait until it's time for
                   ; the next frame to execute.  In some cases the code may take longer than one
                   ; frame to execute, e.g. drawing the high score list takes 44 frames.  At 128
                   ; frames the IRQ handler will conclude that the main thread has stalled and
                   ; reset the machine.
                   ; 
500f: 46 9f        MainLoop        lsr     exec_flag         ;shift low bit into carry flag
5011: 90 fc                        bcc     MainLoop          ;not set, loop until IRQ handler sets it
5013: a9 00                        lda     #$00
5015: 85 9f                        sta     exec_flag         ;clear flag
5017: 20 3e 50                     jsr     StateMachine      ;execute current function
501a: 20 a6 50                     jsr     ScrollAndChkStart ;check start buttons, and scroll text
                   ; 
501d: a5 f2                        lda     in1_value         ;get modified IN1
501f: 29 40                        and     #%01000000        ;check the self-test switch
5021: d0 ec                        bne     MainLoop          ;not in test mode, loop
5023: 4c 4b 7b                     jmp     HandleReset       ;test mode, reset the system

                   ; 
                   ; Func $04: plays one frame of the game.
                   ; 
5026: 20 fb 55     F_PlayGame      jsr     LaunchAttacks     ;launch missiles, bombs, and fliers
5029: 20 5a 52                     jsr     CheckFireButtons  ;fire ABMs
502c: 20 46 61                     jsr     UpdateFlier       ;move the flier
502f: 20 d0 53                     jsr     UpdateAbms        ;move ABMs
5032: 20 21 53                     jsr     UpdateIcbms       ;move ICBMs
5035: 20 58 54                     jsr     UpdateExplosions  ;update explosions, check collisions
5038: 20 32 51                     jsr     UpdateCrossPosn   ;move crosshairs
503b: 4c ee 59                     jmp     CheckWaveDone     ;see if the wave is done yet

                   ; 
                   ; Calls the function specified by $91.
                   ; 
503e: a6 91        StateMachine    ldx     func_index        ;get index of function to execute
5040: bd 4a 50                     lda     :jump_table+1,x   ;get address
5043: 48                           pha                       ;push on stack as if it were
5044: bd 49 50                     lda     :jump_table,x     ; a return address
5047: 48                           pha
5048: 60                           rts                       ;jump to function

5049: d0 5a        :jump_table     .dd2    F_InitGame-1      ;$00
504b: 0f 5b                        .dd2    F_PrepWave-1      ;$02
504d: 25 50                        .dd2    F_PlayGame-1      ;$04
504f: 6c 5d                        .dd2    F_GameOver-1      ;$06
5051: e9 5b                        .dd2    F_EndOfWave-1     ;$08
5053: 1f 5c                        .dd2    F_AbmBonusPts-1   ;$0a
5055: 81 5c                        .dd2    F_CityBonusChk-1  ;$0c
5057: a9 5c                        .dd2    F_CityBonusPts-1  ;$0e
5059: 0b 5d                        .dd2    F_WaveDoneUpdate-1 ;$10
505b: a1 5d                        .dd2    F_ShowEndTimes-1  ;$12
505d: 81 5a                        .dd2    F_ShowTitle-1     ;$14
505f: b8 5a                        .dd2    F_TitleExpls-1    ;$16
5061: 3e 75                        .dd2    F_HighScoreCheck-1 ;$18
5063: 44 76                        .dd2    F_EnterInitials-1 ;$1a
5065: ff 76                        .dd2    F_ShowHighScores-1 ;$1c
5067: 85 77                        .dd2    F_HalfCredit-1    ;$1e
5069: 9e 77                        .dd2    F_WaitFullCredit-1 ;$20
506b: 6c 50                        .dd2    F_PrePlayPause-1  ;$22

                   ; 
                   ; Func $22: pause between waves or other slow transition.
                   ; 
                   ; After a delay specified in $af, the function index ($91) is set to the value
                   ; in $92.  The player is allowed to move the crosshairs around during this time.
                   ; 
506d: a5 ca        F_PrePlayPause  lda     game_frame_ctr    ;get frame counter
506f: 29 03                        and     #%00000011        ;discard all but the low 2 bits
5071: d0 30                        bne     Jmp_UpdCrossPosn  ;skip the rest 3/4ths of the time, so the counter
5073: a4 af                        ldy     frame4_delay_ctr  ; decs once per 4 game frames (15Hz)
5075: f0 02                        beq     :AtZero           ;already at zero, branch
5077: c6 af                        dec     frame4_delay_ctr  ;decrement counter
5079: d0 28        :AtZero         bne     Jmp_UpdCrossPosn  ;if not at zero yet, skip the rest
                   ; 
507b: a5 92                        lda     next_func_index   ;get index of next function
507d: 85 91                        sta     func_index        ;set as current index
507f: c9 04                        cmp     #FN_PLAY_GAME     ;are we switching to the game-play function?
5081: d0 20                        bne     Jmp_UpdCrossPosn  ;no, branch
5083: a5 93                        lda     play_mode_flag    ;is a game in progress?
5085: f0 1c                        beq     Jmp_UpdCrossPosn  ;no, branch
                   ; 
5087: a9 00                        lda     #$00              ;erase lines with these messages
5089: 20 49 6a                     jsr     EraseMessageLine  ;"PLAYER"
508c: a9 06                        lda     #$06
508e: 20 49 6a                     jsr     EraseMessageLine  ;"X POINTS"
5091: a9 1e                        lda     #$1e
5093: 20 49 6a                     jsr     EraseMessageLine  ;"ATARI (C)(P) 1980"
5096: a9 15                        lda     #$15
5098: 20 49 6a                     jsr     EraseMessageLine  ;"DEFEND CITIES"
509b: a9 00                        lda     #$00
509d: 85 fa                        sta     fire_buttons      ;reset fire buttons
509f: 85 95                        sta     trk_v_delta       ;clear trackball movement delta
50a1: 85 94                        sta     trk_h_delta
                   Jmp_UpdCrossPosn
50a3: 4c 32 51                     jmp     UpdateCrossPosn   ;update the position of the crosshairs

                   ; 
                   ; Updates the scrolling text, and checks to see if a start button has been
                   ; pressed.  If a game is in progress, redraws the score and the 1UP/2UP
                   ; indicator if things aren't too busy.
                   ; 
                   ; Called on every iteration of the main loop, regardless of which function is
                   ; active.
                   ; 
                   ScrollAndChkStart
50a6: a5 93                        lda     play_mode_flag    ;is a game in progress?
50a8: d0 55                        bne     :NotFree          ;yes, skip most of this
                   ; 
50aa: 20 5d 5f                     jsr     ScrollText        ;update the scrolling text display
                   ; 
50ad: a5 66                        lda     credit_count      ;get number of credits
50af: f0 32                        beq     :NoCredits        ;if zero, branch
50b1: a0 00                        ldy     #$00              ;init number of players to zero
50b3: c9 02                        cmp     #$02              ;do we have at least 2 credits?
50b5: a5 f1                        lda     in0_value         ;get IN0
50b7: 49 ff                        eor     #$ff              ;invert bits
50b9: 29 18                        and     #%00011000        ;check player 1+2 start buttons
50bb: f0 23                        beq     :NoStart          ;not pressed, branch
50bd: b0 05                        bcs     :TwoCreds         ;2+ credits, branch
50bf: 29 10                        and     #%00010000        ;ignore player 2 start
50c1: b8                           clv
50c2: 50 05                        bvc     :OneOrMore        ;(always)

50c4: c8           :TwoCreds       iny                       ;increment number of players
50c5: c6 66                        dec     credit_count      ;deduct 1 credit
50c7: 29 08                        and     #%00001000        ;check if 2-player start pressed
50c9: f0 03        :OneOrMore      beq     :SinglePlayer     ;not, branch
50cb: c6 66                        dec     credit_count      ;deduct a second credit
50cd: c8                           iny                       ;increment number of players
50ce: 98           :SinglePlayer   tya                       ;copy to A-reg (to set flags)
50cf: 85 ae                        sta     num_players       ;set the number of players
50d1: f0 0d                        beq     :NoStart          ;if 0 players (e.g. hit "start 2" w/1 credit), branch
                   ; Start a game.
50d3: a0 ff                        ldy     #$ff
50d5: 84 93                        sty     play_mode_flag    ;set game-in-progress flag
50d7: c8                           iny                       ;Y-reg=0
50d8: 84 68                        sty     recent_coins      ;reset bonus-for-4-coins counter
50da: a9 00                        lda     #FN_INIT_GAME     ;switch to game initialization func
50dc: 85 91                        sta     func_index
50de: c6 ae                        dec     num_players       ;adjust player count from [1,2] to [0,1]
50e0: b8           :NoStart        clv
50e1: 50 12                        bvc     :CheckFree        ;(always)

50e3: a5 67        :NoCredits      lda     coins_inserted    ;get number of coins inserted but not credited
50e5: f0 0e                        beq     :CheckFree        ;none, branch
50e7: a5 91                        lda     func_index        ;what are we doing right now?
50e9: c9 20                        cmp     #FN_WAIT_FULL_CREDIT ;waiting for the other half of a 2-coin credit?
50eb: f0 08                        beq     :CheckFree        ;yes, continue
50ed: c9 12                        cmp     #FN_THE_END       ;showing "the end"?
50ef: f0 04                        beq     :CheckFree        ;yes, continue
50f1: a9 1e                        lda     #FN_HALF_CREDIT   ;ask for another coin
50f3: 85 91                        sta     func_index
                   ; 
50f5: a5 f3        :CheckFree      lda     r10_irq_mod       ;get R10 switches
50f7: 29 03                        and     #%00000011        ;check coins/play setting
50f9: d0 04                        bne     :NotFree          ;not free play, branch
50fb: a9 02                        lda     #$02              ;free play, set credits to 2
50fd: 85 66                        sta     credit_count
                   ; 
50ff: e6 ca        :NotFree        inc     game_frame_ctr    ;advance the game frame counter
5101: a5 9f                        lda     exec_flag         ;was this a slow frame?
5103: d0 1c                        bne     :CheckSlam        ;yes, skip the next couple of things
                   ; 
5105: a5 d4                        lda     score_dirty_flag  ;do we need to redraw the score?
5107: f0 05                        beq     :UpdatePlyrIndic  ;no, branch
5109: a4 b9                        ldy     cur_plyr_num      ;get current player (0/1)
510b: 20 fc 5f                     jsr     PrintPlyrScore    ;print their score
                   ; 
                   :UpdatePlyrIndic
510e: a5 d8                        lda     indic_phase_counter ;has the "1UP" flash counter hit zero?
5110: d0 0f                        bne     :CheckSlam        ;not yet, branch
5112: 20 52 5a                     jsr     UpdateFlashIndic  ;draw/erase based on *current* value
5115: a5 d7                        lda     draw_1up_flag     ;get visibility phase of the player indicator
5117: 49 01                        eor     #%00000001        ;change phase
5119: 85 d7                        sta     draw_1up_flag
511b: aa                           tax                       ;use as index
511c: bd 30 51                     lda     :plyr_indic_phase_tbl,x ;get number of frames to wait before redraw
511f: 85 d8                        sta     indic_phase_counter ;set counter (which is decr by IRQ handler)
                   ; 
5121: a5 5f        :CheckSlam      lda     slam_tone_ctr1    ;are we whining about the slam switch?
5123: f0 04                        beq     :NoWhine          ;no, branch
5125: a9 f0                        lda     #$f0              ;yes, set the second counter
5127: 85 e0                        sta     slam_tone_ctr2    ; (checked for nonzero in the sound code)
5129: a5 e0        :NoWhine        lda     slam_tone_ctr2    ;are we currently whining?
512b: f0 02                        beq     :Return           ;no, bail
512d: c6 e0                        dec     slam_tone_ctr2    ;yes, count it down
512f: 60           :Return         rts

                   ; 
                   ; Player indicator arrow flash timing.
                   :plyr_indic_phase_tbl
5130: 0a                           .dd1    10                ;10 frames of invisible
5131: 32                           .dd1    50                ;50 frames of visible

                   ; 
                   ; Updates the position of the crosshairs, using either the trackball delta or
                   ; the directions from the self-play code.
                   ; 
                   ]trk_h_arg      .var    $b0    {addr/1}
                   ]trk_v_arg      .var    $b1    {addr/1}

5132: a5 a6        UpdateCrossPosn lda     cross_yc          ;are crosshairs visible?
5134: f0 1e                        beq     :Return           ;no, bail
5136: a5 93                        lda     play_mode_flag    ;playing a game?
5138: d0 06                        bne     :GetDelta         ;yes, branch
513a: 20 55 51                     jsr     DemoGamePlay      ;no, demo playing the game
513d: b8                           clv
513e: 50 0e                        bvc     :Move             ;(always)

5140: a0 00        :GetDelta       ldy     #$00              ;zero out the trackball delta
5142: a5 95                        lda     trk_v_delta       ;get vertical delta
5144: 84 95                        sty     trk_v_delta       ;reset to zero
5146: 85 b1                        sta     ]trk_v_arg        ;save in ZP as arg to func
5148: a5 94                        lda     trk_h_delta       ;get horizontal delta
514a: 84 94                        sty     trk_h_delta       ;reset to zero
514c: 85 b0                        sta     ]trk_h_arg        ;save in ZP as arg to func
514e: 20 39 52     :Move           jsr     DoUpdateCrossPosn ;apply trackball to crosshairs
5151: 20 d3 64                     jsr     DrawCrosshairs    ;erase and redraw
5154: 60           :Return         rts

                   ; 
                   ; Moves the crosshairs and fires ABMs during attract mode.
                   ; 
                   ; If there are no ICBMs, this uses the coordinates of an empty ICBM slot.  Empty
                   ; slots have a Y-coord of zero, so the crosshairs will move down toward the
                   ; cities.
                   ; 
                   ; On exit:
                   ;   $b0-b1: crosshairs movement delta
                   ; 
                   ]shift_hi       .var    $98    {addr/1}
                   ]attract_targ   .var    $d0    {addr/1}   ;(re-using a smartbomb global)

5155: a9 00        DemoGamePlay    lda     #$00
5157: 85 b1                        sta     ]trk_v_arg        ;init fake trackball movement
5159: 85 b0                        sta     ]trk_h_arg
515b: a6 d0                        ldx     ]attract_targ     ;get current target of interest
515d: bd 65 01                     lda     icbm_cur_y,x      ;is it a live ICBM?
5160: d0 03                        bne     :LiveTarget       ;yes, branch
5162: 20 e7 51                     jsr     PickIcbmTarget    ;no, find an ICBM to target
5165: a6 d0        :LiveTarget     ldx     ]attract_targ     ;get target index
5167: bd 65 01                     lda     icbm_cur_y,x      ;get current Y coord (will be 0 if inactive)
516a: c5 a6                        cmp     cross_yc          ;is it >= the crosshair position?
516c: b0 05                        bcs     :IcbmAbove        ;yes, branch
516e: a9 fe                        lda     #$fe              ;no, move the crosshairs down
5170: b8                           clv
5171: 50 12                        bvc     :GotV             ;(always)

5173: 38           :IcbmAbove      sec
5174: e5 a6                        sbc     cross_yc          ;compute difference
5176: c9 0e                        cmp     #14               ;14 pixels below?
5178: d0 05                        bne     :Not14            ;no, branch
517a: a9 00                        lda     #$00              ;no vertical movement
517c: b8                           clv
517d: 50 06                        bvc     :GotV

517f: a9 02        :Not14          lda     #$02              ;move crosshairs up two pixels
5181: b0 02                        bcs     :GotV             ; if more than 14 above
5183: a9 fe                        lda     #$fe              ;else move down 2 pixels
5185: 85 b1        :GotV           sta     ]trk_v_arg        ;set vertical movement
                   ; Get the horizontal increment, which is an 8.8 value < 1.0.  We right-shift it
                   ; 4x, then use the low byte.  It would also work to left-shift it 4x then use
                   ; the high byte, which is more representative of what we're trying to do:
                   ; estimate the ICBM's horizontal movement for the next 16 frames.
5187: bd 18 06                     lda     msl_inc_xint+8,x  ;get ICBM's horizontal movement, int part
518a: 85 98                        sta     ]shift_hi         ;copy to ZP
518c: bd 08 06                     lda     msl_inc_xfrac+8,x ;get fractional part
518f: 46 98                        lsr     ]shift_hi         ;shift right 4x
5191: 6a                           ror     A
5192: 46 98                        lsr     ]shift_hi
5194: 6a                           ror     A
5195: 46 98                        lsr     ]shift_hi
5197: 6a                           ror     A
5198: 46 98                        lsr     ]shift_hi
519a: 6a                           ror     A
519b: 18                           clc                       ;add the low byte, which is effectively the
519c: 7d 30 01                     adc     icbm_cur_x,x      ; fractional part x16
519f: c5 a5                        cmp     cross_xc          ;compare it to the current crosshair position
51a1: b0 05                        bcs     :RtOrStay         ;need to move right or not at all; branch
51a3: a9 fe                        lda     #$fe              ;move 2 pixels left
51a5: b8                           clv
51a6: 50 09                        bvc     :GotH             ;(always)

51a8: d0 05        :RtOrStay       bne     :NotEqH           ;branch if we need to move
51aa: a9 00                        lda     #$00              ;we're in the right place, don't move
51ac: b8                           clv
51ad: 50 02                        bvc     :GotH             ;(always)

51af: a9 02        :NotEqH         lda     #$02              ;move 2 pixels right
51b1: 85 b0        :GotH           sta     ]trk_h_arg        ;set horizontal movement
                   ; 
51b3: 05 b1                        ora     ]trk_v_arg        ;do we need to move at all?
51b5: d0 2f                        bne     :Return           ;yes, branch
                   ; We're in the right place.  Fire?
51b7: a5 bc                        lda     active_abm_count  ;get number of in-flight ABMs
51b9: c9 02                        cmp     #$02              ;>= 2?
51bb: b0 29                        bcs     :Return           ;yes, don't fire any more just yet
51bd: a2 02                        ldx     #$02              ;default to silo on right
51bf: a5 a5                        lda     cross_xc          ;get crosshair horizontal position
51c1: c9 60                        cmp     #96               ;>= 96?
51c3: b0 05                        bcs     :Ge96             ;yes, branch
51c5: a2 00                        ldx     #$00              ;no, use left silo
51c7: b8                           clv
51c8: 50 06                        bvc     :Fire

51ca: c9 a0        :Ge96           cmp     #160              ;>= 160?
51cc: b0 02                        bcs     :Fire             ;yes, branch to use silo #2
51ce: a2 01                        ldx     #$01              ;no, use middle silo
                   ; 
51d0: a5 bc        :Fire           lda     active_abm_count  ;get number of in-flight ABMs
51d2: 18                           clc
51d3: 65 8e                        adc     num_explosions    ;add the number of explosions
51d5: c5 8c                        cmp     num_live_icbms    ;compare to number of inbound ICBMs
51d7: b0 0d                        bcs     :Return           ;if ABMs + explosions >= ICBMs, bail
51d9: 20 bd 52                     jsr     LaunchAbm         ;fire!
                   ; 
51dc: a0 00                        ldy     #$00
51de: 84 b0                        sty     ]trk_h_arg        ;hold horizontal position
51e0: 88                           dey                       ;Y-reg=$ff
51e1: 84 b1                        sty     ]trk_v_arg        ;move down one line
51e3: 20 e7 51                     jsr     PickIcbmTarget    ;pick a new target
51e6: 60           :Return         rts

                   ; 
                   ; Chooses an ICBM to target when playing in attract mode.
                   ; 
                   ; Returns the first live ICBM in the list, starting the check with the current
                   ; index so we'll tend to find older ones.  If there are no live ICBMs, this
                   ; leaves $d0 unchanged.
                   ; 
                   ; On entry:
                   ;   $d0: index of previous target
                   ; 
                   ; On exit:
                   ;   $d0: index of new target
                   ; 
51e7: a6 d0        PickIcbmTarget  ldx     ]attract_targ     ;get current target index
51e9: ca           :Loop           dex                       ;decrement it
51ea: 10 02                        bpl     :StillPos         ;if we didn't roll under, branch
51ec: a2 07                        ldx     #$07              ;start over with the 8th entry
51ee: e4 d0        :StillPos       cpx     ]attract_targ     ;is this the original value?
51f0: d0 01                        bne     :Check            ;no, branch
51f2: 60                           rts                       ;yes, we wrapped around; no ICBMs to shoot

51f3: bd 65 01     :Check          lda     icbm_cur_y,x      ;is this one alive?
51f6: f0 f1                        beq     :Loop             ;no, loop
51f8: 86 d0                        stx     ]attract_targ     ;yes, set it as the next target
51fa: 60                           rts                       ; and return

                   ; 
                   ; Applies trackball movement deltas to the crosshair position for one axis.  The
                   ; value will be clamped to [0,255].
                   ; 
                   ; Trackball movement is accumulated across multiple IRQ events.
                   ; 
                   ; On entry:
                   ;   X-reg: trackball axis (0=horz, 1=vert)
                   ;   $b0-b1: trackball movement deltas (H/V)
                   ; 
                   ; On exit:
                   ;   A-reg: updated crosshair axis position (0-255)
                   ; 
                   ]trk_axis_hi    .var    $9d    {addr/2}
                   ]trk_axis_lo    .var    $b0    {addr/2}

51fb: a0 00        ApplyTrackAxis  ldy     #$00              ;assume positive
51fd: b5 b0                        lda     ]trk_axis_lo,x    ;get value
51ff: 10 01                        bpl     :IsPos            ;is positive, branch
5201: 88                           dey                       ;make negative
5202: 94 9d        :IsPos          sty     ]trk_axis_hi,x    ;sign-extend axis value
                   ; 
5204: a0 06                        ldy     #$06              ;start with shift 6x
5206: a5 fc                        lda     cocktail_flip     ;get cocktail flip mode
5208: 0a                           asl     A                 ;shift the "is a cocktail cabinet" flag into carry
5209: a5 f4                        lda     r8_irq_inv        ;get R8 switches
520b: 29 08                        and     #%00001000        ;get "mini trak-ball" flag
520d: 6a                           ror     A                 ;shift cocktail cabinet flag into high bit
520e: 25 93                        and     play_mode_flag    ;zero it out if game not in progress
5210: f0 01                        beq     :ShiftLoop        ;branch if not cocktail and not mini trak
5212: c8                           iny                       ;cocktail + mini, shift 7x instead
                   ; 
5213: 16 b0        :ShiftLoop      asl     ]trk_axis_lo,x    ;multiply by 64 or 128, yielding
5215: 36 9d                        rol     ]trk_axis_hi,x    ; an 8.8 value
5217: 88                           dey                       ;done with shift?
5218: 10 f9                        bpl     :ShiftLoop        ;no, loop
                   ; 
521a: b5 a3                        lda     cross_frac,x      ;update position of crosshairs
521c: 18                           clc                       ;start with the fractional part
521d: 75 b0                        adc     ]trk_axis_lo,x
521f: 95 a3                        sta     cross_frac,x
5221: b5 a5                        lda     cross_xc,x        ;now add the integer part
5223: 75 9d                        adc     ]trk_axis_hi,x    ;(note we don't actually store the result)
5225: d5 a5                        cmp     cross_xc,x        ;did the integer part change?
5227: f0 0f                        beq     :Return           ;no, bail with result in A-reg
                   ; Test for underflow/overflow.  For example, if we add a positive value but the
                   ; result became smaller, we know we rolled over.
5229: b4 9d                        ldy     ]trk_axis_hi,x    ;was this move in the positive direction?
522b: 10 07                        bpl     :PosMove          ;yes, branch
522d: 90 02                        bcc     :Keep             ;no; if new value < old value, return
522f: a9 00                        lda     #$00              ;underflow, clamp to 0
5231: b8           :Keep           clv
5232: 50 04                        bvc     :Return           ;(always)

5234: b0 02        :PosMove        bcs     :Return           ;if new value >= old value, return
5236: a9 ff                        lda     #$ff              ;overflow, clamp to 255
5238: 60           :Return         rts

                   ; 
                   ; Updates the position of the crosshairs, clamping the movement to min/max
                   ; values from a table.  We don't want the crosshairs to be up in the score area,
                   ; or down with the cities, or wrapping around left and right.
                   ; 
                   ; On entry:
                   ;   $b0-b1: trackball movement deltas
                   ; 
                   DoUpdateCrossPosn
5239: a2 00                        ldx     #$00              ;update the X axis
523b: 20 40 52                     jsr     :UpdateAxis
523e: a2 01                        ldx     #$01              ;update the Y axis
                   ; 
5240: 20 fb 51     :UpdateAxis     jsr     ApplyTrackAxis    ;apply movement deltas
5243: dd 56 52                     cmp     :min_coord,x      ;>= min?
5246: b0 03                        bcs     :GeMin            ;yes, branch
5248: bd 56 52                     lda     :min_coord,x      ;no, clamp
524b: dd 58 52     :GeMin          cmp     :max_coord,x      ;< max?
524e: 90 03                        bcc     :LtMax            ;yes, branch
5250: bd 58 52                     lda     :max_coord,x      ;no, clamp
5253: 95 a5        :LtMax          sta     cross_xc,x        ;store result
5255: 60                           rts

5256: 08           :min_coord      .dd1    8                 ;min X coord
5257: 2d                           .dd1    45                ;min Y coord (above cities)
5258: f7           :max_coord      .dd1    247               ;max X coord
5259: ce                           .dd1    206               ;max Y coord (comfortably below top)

                   ; 
                   ; Checks to see if a fire button is pressed.  If we have ammo in the launcher,
                   ; and there's an available slot for a missile, and there's something to shoot
                   ; at, and things aren't too busy, we launch.
                   ; 
                   CheckFireButtons
525a: a5 93                        lda     play_mode_flag    ;is a game in progress?
525c: f0 57                        beq     :Done             ;no, branch
525e: a5 8d                        lda     wave_icbm_count   ;are there pending ICBMs?
5260: 05 8c                        ora     num_live_icbms    ;or in-flight ICBMs?
5262: 05 db                        ora     wave_sbomb_count  ;or pending smartbombs?
5264: 05 da                        ora     num_live_bombs    ;or in-flight smartbombs?
5266: 0d 6d 01                     ora     flier_cur_y       ;or a flier?
5269: f0 47                        beq     :NoTargets        ;if not, there's nothing to shoot at; branch
                   ; 
526b: a5 8f                        lda     num_expl_abms     ;get number of exploding ABMs
526d: 18                           clc
526e: 65 bc                        adc     active_abm_count  ;add number of in-flight ABMs
5270: c9 10                        cmp     #16               ;>= 16?
5272: b0 10                        bcs     :Br_Overload      ;yes, stop shooting
5274: 65 8c                        adc     num_live_icbms    ;add the number of live ICBMs
5276: 18                           clc
5277: 65 da                        adc     num_live_bombs    ;and the number of live bombs
5279: 18                           clc
527a: ae 6d 01                     ldx     flier_cur_y       ;is there an active flier?
527d: f0 01                        beq     :NoFlier          ;no, branch
527f: 38                           sec                       ;add one for the flier
5280: 65 90        :NoFlier        adc     num_expl_icbms    ;add number of exploding ICBMs
5282: c9 14                        cmp     #20               ;>= 20?
5284: b0 1e        :Br_Overload    bcs     :Overload         ;yes, stop shooting
                   ; We're able to fire missiles.  Check buttons and launchers.
5286: a2 02                        ldx     #$02              ;check all three fire buttons
5288: a5 fa        :ButtonLoop     lda     fire_buttons      ;get fire button state
528a: 3d ba 52                     and     :button_bits,x    ;bit set?
528d: f0 0f                        beq     :NextButton       ;no, branch
528f: b5 a0                        lda     abms_left,x       ;do we have ammo in this launcher?
5291: f0 06                        beq     :NoAmmo           ;no, branch
5293: 20 bd 52                     jsr     LaunchAbm         ;yes, launch an ABM
5296: b8                           clv
5297: 50 05                        bvc     :NextButton

5299: a9 80        :NoAmmo         lda     #SFX_CANT_FIRE    ;notify player that launcher has no ammo
529b: 20 ec 79                     jsr     StartSfx
529e: ca           :NextButton     dex                       ;checked all 3 buttons?
529f: 10 e7                        bpl     :ButtonLoop       ;no, branch
52a1: b8                           clv
52a2: 50 0b                        bvc     :Br_Done          ;bail

52a4: a5 fa        :Overload       lda     fire_buttons      ;get fire button state
52a6: 29 07                        and     #%00000111        ;is one of the buttons pressed?
52a8: f0 05                        beq     :Br_Done          ;no, nothing to do
52aa: a9 80                        lda     #SFX_CANT_FIRE    ;yes, notify player that launch isn't possible
52ac: 20 ec 79                     jsr     StartSfx
52af: b8           :Br_Done        clv
52b0: 50 03                        bvc     :Done             ;bail

52b2: 20 72 65     :NoTargets      jsr     HideCrosshairs    ;nothing to shoot, hide crosshairs
52b5: a9 00        :Done           lda     #$00              ;reset fire button state
52b7: 85 fa                        sta     fire_buttons
52b9: 60                           rts

52ba: 04 02 01     :button_bits    .bulk   $04,$02,$01       ;bit masks for the three fire buttons

                   ; 
                   ; Handles an ABM launch by finding an open slot and filling in the blanks.
                   ; 
                   ; On entry:
                   ;   X-reg: launch silo index (0-2)
                   ; 
52bd: a0 07        LaunchAbm       ldy     #$07              ;8 slots for in-flight ABMs
52bf: b9 5d 01     :Loop           lda     abm_cur_y,y       ;is this slot free?
52c2: d0 59                        bne     :Next             ;no, branch
52c4: 84 97                        sty     slot_index        ;yes, save index
                   ; 
52c6: a9 00                        lda     #$00              ;initialize ABM slot
52c8: 99 4d 01                     sta     abm_cur_yfrac,y   ;set increment fraction to zero
52cb: 99 18 01                     sta     abm_cur_xfrac,y
52ce: bd e8 60                     lda     abm_silo_x_tbl,x  ;get X coord of center of silo
52d1: 99 82 01                     sta     abm_start_x_arr,y ;set as initial X
52d4: 99 28 01                     sta     abm_cur_x,y       ;set as current X
52d7: bd f4 60                     lda     abm_silo_top_tbl,x ;get Y coord from table
52da: 99 92 01                     sta     abm_start_y_arr,y ;set as initial Y
52dd: 99 5d 01                     sta     abm_cur_y,y       ;set as current Y
                   ; 
52e0: a5 a5                        lda     cross_xc          ;get X coord of crosshairs
52e2: 99 a2 01                     sta     abm_targ_x_arr,y  ;set as target X
52e5: a5 a6                        lda     cross_yc          ;get Y coord of crosshairs
52e7: 99 b2 01                     sta     abm_targ_y_arr,y  ;set as target Y
52ea: 20 7c 65                     jsr     DrawTargetX       ;draw a flashing 'X' at the target location
52ed: 20 f6 58                     jsr     CalcMissilePath   ;configure missile path
                   ; 
52f0: a5 93                        lda     play_mode_flag    ;game in progress?
52f2: f0 14                        beq     :SkipText1        ;no, don't show "LOW"
52f4: b5 a0                        lda     abms_left,x       ;get number of ABMs remaining in silo
52f6: c9 04                        cmp     #$04              ;down to 4?
52f8: d0 09                        bne     :Not4             ;no, branch
52fa: 8a                           txa                       ;put silo number in A-reg
52fb: 20 24 68                     jsr     PrintLauncherLow  ;print "LOW"
52fe: a9 01                        lda     #SFX_SILO_LOW     ;"silo low" warning sound
5300: b8                           clv
5301: 50 02                        bvc     :PlaySound        ;(always)

5303: a9 04        :Not4           lda     #SFX_ABM_LAUNCH   ;"abm launched" sound
5305: 20 ec 79     :PlaySound      jsr     StartSfx
5308: d6 a0        :SkipText1      dec     abms_left,x       ;decrement number of ABMs available in silo
530a: d0 08                        bne     :SkipText2        ;if not zero, don't print "OUT"
530c: a5 93                        lda     play_mode_flag    ;game in progress?
530e: f0 04                        beq     :SkipText2        ;no, don't show "OUT"
5310: 8a                           txa                       ;put silo number in A-reg
5311: 20 36 68                     jsr     PrintLauncherOut  ;print "OUT"
5314: 8a           :SkipText2      txa                       ;put silo number in A-reg
5315: a8                           tay                       ;transfer to Y-reg
5316: 20 d1 67                     jsr     EraseAmmo         ;erase one ammo icon from silo
5319: 98                           tya                       ;put silo number in A-reg
531a: aa                           tax                       ;put it back in X-reg
531b: a0 00                        ldy     #$00              ;make the next BPL fall through
531d: 88           :Next           dey
531e: 10 9f                        bpl     :Loop             ;loop if we're still looking for a slot
5320: 60                           rts

                   ; 
                   ; Updates the position of ICBMs and smartbombs, and draws them.
                   ; 
                   ; Sets the "live incoming ordnance" counters at $8c/$da, and min/max Y-coord
                   ; values.
                   ; 
5321: a5 b3        UpdateIcbms     lda     icbm_move_ctr+1   ;at zero?
5323: f0 05                        beq     :AtZero           ;yes, branch
5325: c6 b3                        dec     icbm_move_ctr+1   ;no, decrement whole part
5327: b8                           clv                       ;and bail
5328: 50 4e                        bvc     :Return           ;(always)

532a: a5 b2        :AtZero         lda     icbm_move_ctr     ;add 8.8 delay value to counter
532c: 18                           clc
532d: 65 b7                        adc     icbm_move_delay
532f: 85 b2                        sta     icbm_move_ctr
5331: a5 b3                        lda     icbm_move_ctr+1
5333: 65 b8                        adc     icbm_move_delay+1
5335: 85 b3                        sta     icbm_move_ctr+1
                   ; 
5337: a2 07                        ldx     #$07              ;walk through 8 icbms / bombs
5339: 86 dc                        stx     incoming_ctr
533b: a9 ff                        lda     #$ff
533d: 85 bd                        sta     incoming_min_y    ;init min Y (not used)
533f: 85 c2                        sta     mirv_slot
5341: a9 00                        lda     #$00
5343: 85 8c                        sta     num_live_icbms    ;init live ICBM counter
5345: 85 da                        sta     num_live_bombs    ;init live smartbomb counter
5347: 85 be                        sta     incoming_max_y    ;init max Y (used for decisions)
                   ; 
5349: a6 dc        :Loop           ldx     incoming_ctr      ;get counter
534b: bd 65 01                     lda     icbm_cur_y,x      ;see if this ICBM is active
534e: f0 1a                        beq     :NextInc          ;no, branch
5350: 8a                           txa                       ;put counter in A-reg
5351: 18                           clc
5352: 69 08                        adc     #$08              ;add 8 to get missile/bomb slot index
5354: 85 97                        sta     slot_index        ;save to ZP
                   ; 
5356: bd 65 01                     lda     icbm_cur_y,x      ;get current Y position
5359: c5 bd                        cmp     incoming_min_y    ;is it less than the minimum?
535b: b0 02                        bcs     :NotLowest        ;no, branch
535d: 85 bd                        sta     incoming_min_y    ;yes, update min value
535f: c5 be        :NotLowest      cmp     incoming_max_y    ;is it >= the maximum?
5361: 90 02                        bcc     :NotGe            ;no, branch
5363: 85 be                        sta     incoming_max_y    ;yes, update max value
5365: 25 93        :NotGe          and     play_mode_flag    ;set to zero if not playing a game
5367: 20 79 53                     jsr     DoMoveInc         ;erase / move / draw missile or bomb
                   ; 
536a: c6 dc        :NextInc        dec     incoming_ctr      ;move to next slot
536c: 10 db                        bpl     :Loop             ;loop if not done
                   ; 
536e: a4 8e                        ldy     num_explosions    ;get number of active explosions
5370: c0 0c                        cpy     #12               ;< 12?
5372: 90 04                        bcc     :Return           ;yes, branch
5374: a9 ff                        lda     #$ff              ;no, disallow MIRV split
5376: 85 c2                        sta     mirv_slot
5378: 60           :Return         rts

                   ; 
                   ; Moves an incoming ICBM / bomb.  Looks for a MIRV slot candidate.
                   ; 
                   ; The MIRV check is peculiar.  If the maximum Y coordinate we've seen so far is
                   ; within a certain range, we flag this slot as the MIRV candidate ($c2).  If, on
                   ; the next call, the max is still in the right range, we overwrite the previous
                   ; value, regardless of the current ICBM's altitude.
                   ; 
                   ; This does not ensure that the MIRV candidate is in the middle altitude range. 
                   ; It just requires that the current or any previous missile is in the middle of
                   ; the screen (between 128 and 159).  If a high-altitude ICBM appears early in
                   ; the table, splitting will be blocked.  (The presence of a high-altitude ICBM
                   ; generally halts production, so that's not actually limiting.)  It prevents
                   ; splitting when all missiles are below 128, but any missile can split when any
                   ; other missile is above 128.
                   ; 
                   ; Skipping the slot update when $c2 is already set would be more deterministic,
                   ; but perhaps that's undesirable.  (Bug?)
                   ; 
                   ; On entry:
                   ;   A-reg: largest Y coordinate seen so far, or 0 if in attract mode
                   ;   X-reg: ICBM slot number (0-7) to update
                   ; 
5379: a8           DoMoveInc       tay
537a: a5 d9                        lda     smartbomb_mask    ;get icbm vs. smartbomb bits
537c: 3d f7 60                     and     single_bits,x     ;check current slot
537f: d0 13                        bne     :UpdateBomb       ;bit is set, smartbomb; branch
5381: c0 80                        cpy     #128              ;is max height seen < 128?
5383: 90 06                        bcc     :NotMirv          ;yes, branch
5385: c0 a0                        cpy     #160              ;is max height seen >= 160?
5387: b0 02                        bcs     :NotMirv          ;yes, branch
5389: 86 c2                        stx     mirv_slot         ;128 >= max > 160, or max==0; save slot num
538b: 20 f7 66     :NotMirv        jsr     DrawMslTrail      ;overwrite the missile head pixel with a trail pixel
538e: 20 11 54                     jsr     MoveMissile       ;move ICBM
5391: b8                           clv
5392: 50 06                        bvc     :CheckExplode     ;(always)

5394: 20 ac 77     :UpdateBomb     jsr     EraseSmartBomb    ;erase the smartbomb
5397: 20 0d 63                     jsr     MoveSmartBomb     ;update its position
                   ; 
539a: a6 dc        :CheckExplode   ldx     incoming_ctr      ;get ICBM slot number (0-7)
539c: 90 1d                        bcc     :NoExplod         ;branch if missile has not reached target
                   ; 
539e: a5 d9                        lda     smartbomb_mask    ;get icbm vs. smartbomb bits
53a0: 3d f7 60                     and     single_bits,x     ;check current slot
53a3: f0 06                        beq     :DetonateIcbm     ;bit clear, icbm; branch
53a5: 20 e9 7a                     jsr     DisableBombSound
53a8: b8                           clv
53a9: 50 07                        bvc     :Explode          ;(always)

53ab: a6 97        :DetonateIcbm   ldx     slot_index        ;get the slot index (8-15)
53ad: 20 86 66                     jsr     EraseTrail        ;erase the ICBM trail
53b0: a6 dc                        ldx     incoming_ctr      ;get the ICBM index (0-7)
                   ; 
53b2: 20 6b 58     :Explode        jsr     CreateExplosion   ;create an explosion
53b5: 20 9f 55                     jsr     CheckGroundHit    ;see if it hit a city or silo
53b8: b8                           clv
53b9: 50 14                        bvc     :Return           ;(always)

53bb: a5 d9        :NoExplod       lda     smartbomb_mask    ;get icbm vs. smartbomb bits
53bd: 3d f7 60                     and     single_bits,x     ;check current slot
53c0: d0 08                        bne     :CountBomb        ;bit is set, smartbomb; branch
53c2: 20 f0 66                     jsr     DrawMslHead       ;draw the head of the missile
53c5: e6 8c                        inc     num_live_icbms    ;keep track of how many are active
53c7: b8                           clv
53c8: 50 05                        bvc     :Return           ;(always)

53ca: 20 a8 77     :CountBomb      jsr     DrawSmartBomb     ;draw the smartbomb
53cd: e6 da                        inc     num_live_bombs    ;keep track of how many are active
53cf: 60           :Return         rts

                   ; 
                   ; Updates the positions of all ABMs.  When they reach their target, we erase the
                   ; trail and create an explosion.
                   ; 
                   ]abm_move_ctr   .var    $d5    {addr/1}

53d0: a9 00        UpdateAbms      lda     #$00
53d2: 85 bc                        sta     active_abm_count  ;reset active ABM count
53d4: a2 07                        ldx     #7                ;player can have up to 8 ABMs in air at once
53d6: bd 5d 01     :AbmLoop        lda     abm_cur_y,x       ;get ABM Y-coord
53d9: f0 32                        beq     :NextAbm          ;inactive slot, branch
53db: 86 97                        stx     slot_index        ;store in slot index for other functions
53dd: a0 02                        ldy     #$02              ;default to moving 3x per frame
53df: bd 82 01                     lda     abm_start_x_arr,x ;get start position
53e2: c9 7b                        cmp     #$7b              ;was it fired from the middle launcher?
53e4: d0 02                        bne     :SlowAbm          ;no, use default 3x movement
53e6: a0 06                        ldy     #$06              ;yes, move 7x per frame
53e8: 84 d5        :SlowAbm        sty     ]abm_move_ctr
53ea: 20 f7 66     :Loop           jsr     DrawMslTrail      ;replace head color with trail color
53ed: 20 11 54                     jsr     MoveMissile       ;advance missile one step
53f0: 90 0d                        bcc     :StillMoving      ;if it hasn't reached its target, branch
                   ; ABM has reached destination.  Blow it up.
53f2: 20 92 65                     jsr     EraseTargetX      ;erase the flashing 'X'
53f5: 20 86 66                     jsr     EraseTrail        ;erase the ABM's trail
53f8: 20 6b 58                     jsr     CreateExplosion   ;create an explosion at this location
53fb: a9 00                        lda     #$00
53fd: 85 d5                        sta     ]abm_move_ctr     ;stop movement
                   ; 
53ff: c6 d5        :StillMoving    dec     ]abm_move_ctr     ;done moving this ABM?
5401: 10 e7                        bpl     :Loop             ;not yet, loop
                   ; 
5403: bd 5d 01                     lda     abm_cur_y,x       ;is this ABM still alive?
5406: f0 05                        beq     :NextAbm          ;no, branch
5408: 20 f0 66                     jsr     DrawMslHead       ;finally, draw the missile head
540b: e6 bc                        inc     active_abm_count  ;add one to the active ABM counter
540d: ca           :NextAbm        dex                       ;done with ABMs?
540e: 10 c6                        bpl     :AbmLoop          ;no, loop
5410: 60                           rts

                   ; 
                   ; Updates missile position, and tests whether it has reached its destination. 
                   ; Used for ICBMs and ABMs, and for smartbombs when they're not evading
                   ; explosions.
                   ; 
                   ; The path calculation is imprecise, so we can't simply test to see if the
                   ; current position is equal to the target position.  Instead, we check to see if
                   ; the current update moved us past it.
                   ; 
                   ; On entry:
                   ;   $97: missile slot index (0-15)
                   ; 
                   ; On exit:
                   ;   C-flag: set if missile has reached its target
                   ; 
5411: a4 97        MoveMissile     ldy     slot_index        ;get missile slot index (0-15)
5413: b9 18 01                     lda     abm_cur_xfrac,y   ;get X position, fractional part
5416: 18                           clc
5417: 79 00 06                     adc     msl_inc_xfrac,y   ;add increment
541a: 99 18 01                     sta     abm_cur_xfrac,y
541d: b9 28 01                     lda     abm_cur_x,y       ;get X position, integer part
5420: 79 10 06                     adc     msl_inc_xint,y    ;add increment
5423: 99 28 01                     sta     abm_cur_x,y
                   ; 
5426: b9 4d 01                     lda     abm_cur_yfrac,y   ;get Y position, fractional part
5429: 18                           clc
542a: 79 20 06                     adc     msl_inc_yfrac,y   ;add increment
542d: 99 4d 01                     sta     abm_cur_yfrac,y
5430: b9 5d 01                     lda     abm_cur_y,y       ;get Y position, integer part
5433: 79 30 06                     adc     msl_inc_yint,y    ;add increment
5436: 99 5d 01                     sta     abm_cur_y,y
                   ; See if we've reached the target.  We don't stop when X or Y matches, but
                   ; rather when we've moved past it.  To understand the calculation, suppose an
                   ; ICBM has a target altitude of 22.  We compare by computing (current - target);
                   ; if the result is positive, we haven't reached it yet.  For an ABM moving
                   ; upward, a negative result would indicate we haven't reached the target.  We
                   ; can handle both cases by comparing the post-compare carry flag to the sign of
                   ; the increment.
5439: d9 b2 01                     cmp     abm_targ_y_arr,y  ;compare current Y coord to target Y coord
543c: f0 08                        beq     :YEqual           ;if they're equal, branch
543e: 6a                           ror     A                 ;rotate carry into high bit
543f: 59 30 06                     eor     msl_inc_yint,y    ;EOR with sign bit of increment
5442: 10 02                        bpl     :YEqual           ;carry matches sign bit, branch
5444: 38                           sec                       ;carry doesn't match, we've passed it
5445: 60                           rts

5446: b9 28 01     :YEqual         lda     abm_cur_x,y       ;get the current X coordinate
5449: d9 a2 01                     cmp     abm_targ_x_arr,y  ;compare to target coordinate
544c: f0 08                        beq     :StillGoing       ;if they're equal, branch
544e: 6a                           ror     A                 ;rotate carry into high bit
544f: 59 10 06                     eor     msl_inc_xint,y    ;EOR with sign bit of increment
5452: 10 02                        bpl     :StillGoing       ;carry matches sign bit, branch
5454: 38                           sec                       ;carry doesn't match, we've passed it
5455: 60                           rts

5456: 18           :StillGoing     clc
5457: 60                           rts

                   ; 
                   ; Updates explosions and checks for collisions.
                   ; 
                   ; There are 20 sets of explosions, which are updated every 5 frames.  To keep
                   ; performance from bogging down when lots of stuff is blowing up, the updates
                   ; are distributed into 5 sets of 4 explosions, and we update a different set on
                   ; every frame.  This means we only check for *collisions* every 5 frames, so
                   ; it's possible for a missile to sneak through an explosion if it's moving
                   ; quickly.
                   ; 
                   ; Explosion slots are assigned in round-robin order.  There isn't a fixed
                   ; association between missile slots and explosion slots.
                   ; 
                   • Clear variables
                   ]after_radius   .var    $9a    {addr/1}
                   ]expl_xc        .var    $9b    {addr/1}
                   ]expl_yc        .var    $9c    {addr/1}
                   ]before_radius  .var    $b5    {addr/1}

                   UpdateExplosions
5458: a4 b4                        ldy     exp_seq_index     ;get sequence index
545a: 88                           dey                       ;move to next set
545b: 10 02                        bpl     :Ge0              ;haven't dropped below zero yet; branch
545d: a0 04                        ldy     #4                ;restart on set 4
545f: 84 b4        :Ge0            sty     exp_seq_index     ;save updated sequence index
5461: a5 8e                        lda     num_explosions    ;is anything exploding?
5463: f0 4b                        beq     :Return           ;no, bail
                   ; 
5465: be b2 54                     ldx     :expl_set_tbl,y   ;get index of first (actually last) entry in set
5468: bd 6e 01     :ExplLoop       lda     expl_ypos,x       ;is this slot active?
546b: f0 3a                        beq     :NextExpl         ;no, branch
546d: 85 9c                        sta     ]expl_yc          ;yes, save Y coord to ZP
546f: fe c2 01                     inc     expl_radius_idx_arr,x ;advance the index
5472: bd c2 01                     lda     expl_radius_idx_arr,x ;get the value
5475: 29 7f                        and     #$7f              ;strip high bit
5477: a8                           tay                       ;copy to Y-reg for later
5478: c9 1b                        cmp     #$1b              ;have we reached the end of the explosion array?
547a: 90 16                        bcc     :Draw             ;not yet, branch
                   ; 
547c: bd c2 01                     lda     expl_radius_idx_arr,x ;does the index have its high bit set?
547f: 30 05                        bmi     :Icbm             ;yes, it was an ICBM; branch
5481: c6 8f                        dec     num_expl_abms     ;no, was ABM; decrement exploding ABM count
5483: b8                           clv
5484: 50 02                        bvc     :DecExpl          ;(always)

5486: c6 90        :Icbm           dec     num_expl_icbms    ;decrement number of exploding ICBMs
5488: c6 8e        :DecExpl        dec     num_explosions    ;decrement number of explosions
548a: a9 00                        lda     #$00
548c: 9d 6e 01                     sta     expl_ypos,x       ;mark the slot as available
548f: b8                           clv
5490: 50 15                        bvc     :NextExpl         ;(always)

5492: bd 39 01     :Draw           lda     expl_xpos,x       ;get explosion X coord
5495: 85 9b                        sta     ]expl_xc
5497: b9 b7 54                     lda     :expl_radii_tbl,y ;get current size
549a: 85 b5                        sta     ]before_radius
549c: b9 b8 54                     lda     :expl_radii_tbl+1,y ;get new size
549f: 85 9a                        sta     ]after_radius
54a1: 20 71 5e                     jsr     DrawExplosion     ;draw or erase a ring of the explosion
54a4: 20 d4 54                     jsr     CheckCollision    ;check for collisions
                   ; 
54a7: ca           :NextExpl       dex                       ;move to next entry
54a8: a4 b4                        ldy     exp_seq_index     ;get set index
54aa: 8a                           txa
54ab: d9 b1 54                     cmp     :expl_set_tbl-1,y ;have we reached the start of the next set?
54ae: d0 b8                        bne     :ExplLoop         ;not yet, loop
54b0: 60           :Return         rts

                   ; 
                   ; Explosion set end values.  The 20 explosion counters are broken into 5 sets:
                   ; 0-3, 4-7, 8-11, 12-15, 16-19.
54b1: ff                           .dd1    $ff
54b2: 03 07 0b 0f+ :expl_set_tbl   .bulk   $03,$07,$0b,$0f,$13
                   ; 
                   ; Explosion radii.  The explosion radius index array at $01c2 holds an index
                   ; into this table.  The index ranges from $00-1b.
                   ; 
54b7: 00 00 02 03+ :expl_radii_tbl .bulk   $00,$00,$02,$03,$04,$05,$06,$07,$08,$09,$0a,$0b,$0c,$0d
54c5: 0d 0c 0b 0a+                 .bulk   $0d,$0c,$0b,$0a,$09,$08,$07,$06,$05,$04,$03,$02,$01,$00
54d3: 00                           .dd1    $00               ;+$1c not actually used?

                   ; 
                   ; Checks for collisions between an ICBM/smartbomb and an explosion.
                   ; 
                   ; This doesn't check for collisions below line 33.  The crosshairs are limited
                   ; to line 45+, and explosions have a max radius of 13, so an explosion at or
                   ; below line 32 should only be possible when a city or silo is being destroyed. 
                   ; The test is probably there so that, if multiple missiles target a city, the
                   ; explosions don't stack up above it.
                   ; 
                   ; We only check the ICBM/smartbomb table for collisions, not ABMs, which allows
                   ; the player to shoot through explosion clouds.  The flier's X/Y position is
                   ; stored at the end of the ICBM table, so we can handle it by walking through 9
                   ; entries instead of 8.
                   ; 
                   ; On entry:
                   ;   $9a: explosion radius
                   ;   $9b: explosion X coord
                   ;   $9c: explosion Y coord
                   ; 
                   ; On exit:
                   ;   (X-reg preserved)
                   ; 
                   ]icbm_xc        .var    $9d    {addr/1}
                   ]icbm_yc        .var    $9e    {addr/1}
                   ]expl_radius    .var    $d5    {addr/1}

54d4: 8a           CheckCollision  txa                       ;preserve X-reg on stack
54d5: 48                           pha
54d6: a5 9a                        lda     ]after_radius     ;copy explosion radius
54d8: 85 d5                        sta     ]expl_radius
54da: a0 08                        ldy     #$08              ;handle 8 ICBMs + 1 flier
54dc: a5 9c                        lda     ]expl_yc          ;get explosion Y coordinate
54de: c9 21                        cmp     #33               ;< 33? (crosshair min is 45)
54e0: 90 5e                        bcc     :RxReturn         ;yes, skip collision checking for this one
                   ; 
54e2: b9 65 01     :UnitLoop       lda     icbm_cur_y,y      ;get Y-coord of ICBM/bomb/flier
54e5: f0 56                        beq     :NextIcbm         ;slot not in use, move to next
54e7: 85 9e                        sta     ]icbm_yc          ;store in ZP
54e9: b9 30 01                     lda     icbm_cur_x,y      ;copy X-coord to ZP
54ec: 85 9d                        sta     ]icbm_xc
54ee: c0 08                        cpy     #$08              ;are we testing the flier?
54f0: d0 05                        bne     :NotFlier         ;no, branch
54f2: a9 06                        lda     #$06              ;use size=6
54f4: b8                           clv
54f5: 50 0e                        bvc     :GotSize          ;(always)

54f7: a5 d9        :NotFlier       lda     smartbomb_mask    ;get smartbomb bit mask
54f9: 39 f7 60                     and     single_bits,y     ;is this entry a bomb?
54fc: d0 05                        bne     :IsBomb
54fe: a9 01                        lda     #$01              ;use size=1
5500: b8                           clv
5501: 50 02                        bvc     :GotSize          ;(always)

5503: a9 03        :IsBomb         lda     #$03              ;use size=3
                   ; 
5505: 18           :GotSize        clc
5506: 65 d5                        adc     ]expl_radius      ;add missile/bomb/flier size to explosion radius
5508: 85 9a                        sta     ]after_radius     ;store in ZP
550a: 20 ab 58                     jsr     CheckDist         ;see if missile is inside explosion radius
550d: b0 2e                        bcs     :NextIcbm         ;it's not, branch
                   ; 
550f: 98                           tya                       ;preserve index
5510: 48                           pha
5511: 18                           clc
5512: 69 08                        adc     #$08              ;add 8 to get general missile slot index
5514: 85 97                        sta     slot_index
5516: c4 c2                        cpy     mirv_slot         ;was this the MIRV candidate?
5518: d0 04                        bne     :NotMirv          ;no, branch
551a: a9 ff                        lda     #$ff              ;yes, clear the MIRV candidate
551c: 85 c2                        sta     mirv_slot
551e: c0 08        :NotMirv        cpy     #$08              ;is this the flier?
5520: 90 06                        bcc     :NotFlier2        ;no, branch
5522: 20 43 55                     jsr     FlierHit          ;destroy the flier
5525: b8                           clv
5526: 50 10                        bvc     :PrepExpl         ;(always)

5528: a5 d9        :NotFlier2      lda     smartbomb_mask    ;get the smartbomb mask
552a: 39 f7 60                     and     single_bits,y     ;see if this was a smartbomb
552d: d0 06                        bne     :BombHit          ;it was, branch
552f: 20 50 55                     jsr     IcbmHit           ;no, was an ICBM; destroy it
5532: b8                           clv
5533: 50 03                        bvc     :PrepExpl         ;(always)

5535: 20 66 55     :BombHit        jsr     SmartBombHit      ;destroy the smartbomb, then fall through
                   ; 
                   ; Create an explosion for whatever was hit.  This allows nifty chain reactions,
                   ; and means we don't have to write code to erase the flier.
5538: 20 6b 58     :PrepExpl       jsr     CreateExplosion   ;create an explosion where the missile/bomb/flier was
553b: 68                           pla                       ;restore index
553c: a8                           tay
553d: 88           :NextIcbm       dey                       ;move to next entry
553e: 10 a2                        bpl     :UnitLoop         ;if not done, loop
                   ; 
5540: 68           :RxReturn       pla                       ;restore X-reg
5541: aa                           tax
5542: 60                           rts

5543: 20 f8 7a     FlierHit        jsr     DisableFlierSound ;stop flier sound
5546: a9 00                        lda     #$00
5548: 85 c9                        sta     flier_fire_ctr    ;reset cooldown
554a: 85 e3                        sta     flier_charge_ctr
554c: a2 03                        ldx     #$03              ;multiply base value by 4 (100 * mult)
554e: d0 20                        bne     AddToScore        ;(always)

5550: c6 8c        IcbmHit         dec     num_live_icbms    ;decrement the active ICBM count
5552: 20 7b 55                     jsr     UpdateTargetSet   ;update set of targeted cities/silos
5555: a6 97                        ldx     slot_index        ;get the slot index (0-15)
5557: 20 86 66                     jsr     EraseTrail        ;erase the missile trail
555a: a2 00                        ldx     #$00              ;multiply base value by 1 (25 * mult)
555c: a4 97                        ldy     slot_index        ;get the slot index (0-15)
555e: b9 5d 01                     lda     abm_cur_y,y       ;get the missile's height
5561: c9 21                        cmp     #33               ;was it >= 33?
5563: b0 0b                        bcs     AddToScore        ;yes, add to score
5565: 60                           rts                       ;no, don't give points for ICBMs that hit the ground

5566: c6 da        SmartBombHit    dec     num_live_bombs    ;decrement the active smartbomb count
5568: 20 7b 55                     jsr     UpdateTargetSet   ;update set of targeted cities/silos
556b: 20 e9 7a                     jsr     DisableBombSound
556e: a2 04                        ldx     #$04              ;multiply base value by 5 (125 * mult)
                   ; 
5570: 20 9f 5f     AddToScore      jsr     SetBaseScoreX     ;compute points earned
5573: a4 b9                        ldy     cur_plyr_num      ;get player number in Y-reg (0/1)
5575: 20 13 60                     jsr     AddBcdToScore     ;add to score
5578: 4c ea 79                     jmp     PlaySound02       ;make some noise

                   ; 
                   ; Updates the target set after a missile or smartbomb is destroyed.  We want to
                   ; zero the flag for the target city/silo if nothing else is targeting it.
                   ; 
557b: a4 97        UpdateTargetSet ldy     slot_index        ;get missile slot index (0-15)
557d: b9 a2 01                     lda     abm_targ_x_arr,y  ;get target X coord
5580: 48                           pha                       ;preserve XC
5581: a9 00                        lda     #$00
5583: 99 a2 01                     sta     abm_targ_x_arr,y  ;set entry for destroyed missile to zero
5586: 68                           pla                       ;restore XC
                   ; 
5587: a0 07                        ldy     #$07              ;walk through all 8 ICBMs
5589: d9 aa 01     :ChkLoop        cmp     icbm_targ_x_arr,y ;is this one targeting the same place?
558c: d0 01                        bne     :NotSame          ;no, branch
558e: 60                           rts                       ;yes, leave flags alone

558f: 88           :NotSame        dey                       ;move to the next slot
5590: 10 f7                        bpl     :ChkLoop          ;if not done, loop
                   ; 
5592: a0 09                        ldy     #$09              ;check 6 cities + 3 silos
5594: 88           :Loop           dey
5595: d9 e2 60                     cmp     city_x_tbl,y      ;was this our target?
5598: d0 fa                        bne     :Loop             ;no, branch
559a: a9 00                        lda     #$00
559c: 4c 47 58                     jmp     UpdateTargetFlag  ;clear flag N

                   ; 
                   ; Checks to see if an exploding missile or smartbomb hit a city or silo.
                   ; 
                   ; On entry:
                   ;   $97: index of ICBM / bomb (8-15)
                   ; 
559f: a4 97        CheckGroundHit  ldy     slot_index        ;get ICBM slot (8-15)
55a1: b9 a2 01                     lda     abm_targ_x_arr,y  ;get X coordinate of target
55a4: a0 09                        ldy     #9                ;check 6 cities and 3 launchers
55a6: 88           :FindCityLoop   dey
55a7: d9 e2 60                     cmp     city_x_tbl,y      ;does it match?
55aa: d0 fa                        bne     :FindCityLoop     ;no, branch
                   ; 
55ac: a9 00                        lda     #$00
55ae: 20 47 58                     jsr     UpdateTargetFlag  ;remove flag from target mask (Y-reg preserved)
55b1: c0 06                        cpy     #$06              ;was target a city?
55b3: 90 21                        bcc     :CityAreaHit      ;yes, branch
55b5: a9 00                        lda     #$00              ;no, silo was hit
55b7: 99 9a 00                     sta     abms_left-6,y     ;zero out the number of ABMs available
55ba: b9 f1 60                     lda     single_bits-6,y   ;get the bit for this silo
55bd: 49 e0                        eor     #%11100000        ;invert to form a mask
55bf: 25 cd                        and     live_silo_mask    ;remove it from set of live silos
55c1: c5 cd                        cmp     live_silo_mask    ;did it change?
55c3: f0 0e                        beq     :WasDead          ;no, silo already dead; branch
55c5: 85 cd                        sta     live_silo_mask    ;update mask
55c7: a9 02                        lda     #SFX_EXPLOSION
55c9: 20 ec 79                     jsr     StartSfx          ;play destruction sound
55cc: 98                           tya                       ;get silo index +6 (6-8)
55cd: 38                           sec
55ce: e9 06                        sbc     #$06              ;reduce to 0-2
55d0: 20 36 68                     jsr     PrintLauncherOut  ;print "OUT" message under it
55d3: b8           :WasDead        clv
55d4: 50 24                        bvc     :Return           ;(always)

55d6: b9 f7 60     :CityAreaHit    lda     single_bits,y     ;get bit for city
55d9: 49 fc                        eor     #%11111100        ;invert the 6 bits used for cities
55db: a4 b9                        ldy     cur_plyr_num      ;get current player number
55dd: 39 c5 00                     and     live_city_mask,y  ;clear the bit for the destroyed city
55e0: d9 c5 00                     cmp     live_city_mask,y  ;did the mask change?
55e3: f0 15                        beq     :Return           ;no, dead city; bail
55e5: 99 c5 00                     sta     live_city_mask,y  ;save updated mask
55e8: b9 c0 00                     lda     num_cities,y      ;get number of cities for this player
55eb: f0 06                        beq     :Already0         ;don't reduce it below 0
55ed: 38                           sec
55ee: e9 01                        sbc     #$01              ;reduce by 1
55f0: 99 c0 00                     sta     num_cities,y      ;save value
55f3: e6 cf        :Already0       inc     cities_destroyed  ;increment city-destroyed counter
55f5: a9 02                        lda     #SFX_EXPLOSION
55f7: 20 ec 79                     jsr     StartSfx          ;play explosion sound
55fa: 60           :Return         rts

                   ; 
                   ; Generates attacks, i.e. launches ICBMs, smartbombs, and fliers.
                   ; 
55fb: a5 8d        LaunchAttacks   lda     wave_icbm_count   ;pending ICBMs?
55fd: 05 db                        ora     wave_sbomb_count  ;pending smart bombs?
55ff: f0 76                        beq     :Return           ;none of the above, bail
                   ; This prevents attacks while the highest ICBM is above (202 - 2 * wave_num). 
                   ; This appears to be a mechanism to cause attacks to trickle in steadily at
                   ; lower waves, but gather up and burst at higher waves.  The height is capped at
                   ; 180, but they limited the result rather than wave number, so at wave 101 the
                   ; result becomes negative (effectively off the top of the screen).  This bug was
                   ; fixed in rev3, by resetting the wave number when it hits 40.
5601: a5 a7                        lda     wave_num          ;get wave number
5603: 0a                           asl     A                 ;double it
5604: 49 ff                        eor     #$ff              ;negate it
5606: 38                           sec
5607: 69 ca                        adc     #202              ;compute (202 - 2*wave_number)
5609: c9 b4                        cmp     #180              ;is it >= 180?
560b: b0 02                        bcs     :Ge180            ;yes, branch
560d: a9 b4                        lda     #180              ;minimum 180
560f: c5 be        :Ge180          cmp     incoming_max_y    ;is highest ICBM above this limit?
5611: 90 64                        bcc     :Return           ;yes, bail
                   ; 
5613: a9 07                        lda     #$07              ;create up to 8 attacks
5615: 38                           sec
5616: ae 6d 01                     ldx     flier_cur_y       ;is there a flier?
5619: f0 01                        beq     :NoFlier          ;no, branch
561b: 18                           clc                       ;subtract one extra for flier
561c: e5 90        :NoFlier        sbc     num_expl_icbms    ;subtract exploding ICBMs
561e: 38                           sec
561f: e5 8c                        sbc     num_live_icbms    ;subtract in-flight ICBMs
5621: 38                           sec
5622: e5 da                        sbc     num_live_bombs    ;subtract in-flight smartbombs
5624: 30 0b                        bmi     :BmiReturn        ;if < 0, don't create anything this frame
5626: 85 de                        sta     atk_create_ctr    ;save to ZP
5628: 18                           clc
5629: 69 0c                        adc     #12               ;add 12
562b: 38                           sec
562c: e5 8f                        sbc     num_expl_abms     ;subtract number of exploding ABMs
562e: 38                           sec
562f: e5 bc                        sbc     active_abm_count  ;subtract number of in-flight ABMs
5631: 30 44        :BmiReturn      bmi     :Return           ;if < 0, we're too congested; don't launch more
5633: c5 de                        cmp     atk_create_ctr    ;is the ABM value >= the attack-create value?
5635: b0 02                        bcs     :KeepAtk          ;yes, keep the attack-create count
5637: 85 de                        sta     atk_create_ctr    ;no, keep the lower ABM-adjusted value
                   ; 
5639: 20 ff 60     :KeepAtk        jsr     CheckCreateFlier  ;create a flier if one is available
563c: a5 de                        lda     atk_create_ctr    ;did we use up all our attacks?
563e: 30 37                        bmi     :Return           ;yes, bail
5640: a5 db                        lda     wave_sbomb_count  ;do we have unlaunched smartbombs?
5642: d0 06                        bne     :TryBomb          ;yes, branch
5644: 20 78 56                     jsr     LaunchIcbms       ;no, go launch ICBMs
5647: b8                           clv
5648: 50 2d                        bvc     :Return           ;(always)

564a: a5 da        :TryBomb        lda     num_live_bombs    ;check number of in-flight bombs
564c: c9 03                        cmp     #$03              ;>= 3?
564e: b0 24                        bcs     :IcbmInstead2     ;yes, branch to launch ICBMs
5650: 18                           clc
5651: 65 8c                        adc     num_live_icbms    ;add number of live ICBMs
5653: c9 05                        cmp     #$05              ;>= 5?
5655: b0 1a                        bcs     :Br_Return        ;branch to launch nothing
5657: a5 8d                        lda     wave_icbm_count   ;get remaining ICBMs in this wave
5659: d0 06                        bne     :RndBomb          ;some left, branch to roll dice
565b: 20 17 57                     jsr     CreateSmartBomb   ;only smartbombs left, go create one
565e: b8                           clv
565f: 50 10                        bvc     :Br_Return        ;(always)

5661: ad 0a 40     :RndBomb        lda     POKEY_RANDOM      ;get a random number
5664: 29 03                        and     #$03              ;reduce to 0-3
5666: d0 06                        bne     :IcbmInstead1     ;75% chance of ICBM
5668: 20 17 57                     jsr     CreateSmartBomb   ;25% of smartbomb
566b: b8                           clv
566c: 50 03                        bvc     :Br_Return        ;(always)

566e: 20 78 56     :IcbmInstead1   jsr     LaunchIcbms       ;launch ICBMs
5671: b8           :Br_Return      clv
5672: 50 03                        bvc     :Return           ;(always)

5674: 20 78 56     :IcbmInstead2   jsr     LaunchIcbms       ;launch ICBMs
5677: 60           :Return         rts

                   ; 
                   ; Launches ICBMs from the top of the screen, the flier, or a MIRV head.
                   ; 
                   ; Various tests are used to limit how much we send out at once.  The most we'll
                   ; send is 4.  (You can see this on wave 1, which launches 4 ICBMs, pauses until
                   ; the max-altitude test passes at $560f, then launches 4 more.)
                   ; 
5678: a2 ff        LaunchIcbms     ldx     #$ff              ;init count to -1
567a: a5 8d                        lda     wave_icbm_count   ;get pending ICBMs for this wave
567c: f0 2a                        beq     :SetAtk           ;none left, branch to clear attack counter
567e: 38                           sec
567f: ad 6d 01                     lda     flier_cur_y       ;do we have a flier?
5682: f0 01                        beq     :NoFlier          ;no, branch
5684: 18                           clc                       ;add 1 for flier
5685: a9 07        :NoFlier        lda     #$07              ;we can have up to 8 bombs/ICBMs
5687: e5 da                        sbc     num_live_bombs    ;subtract number of live bombs
5689: 38                           sec
568a: e5 da                        sbc     num_live_bombs    ;smartbombs count double here
568c: 38                           sec
568d: e5 8c                        sbc     num_live_icbms    ;subtract number of live ICBMs
568f: 30 17                        bmi     :SetAtk           ;if result negative, clear attack counter
                   ; 
5691: aa                           tax                       ;copy result to X-reg
5692: e8                           inx                       ;increment
5693: e0 04                        cpx     #$04              ;is it < 4?
5695: 90 02                        bcc     :Lt4              ;yes, branch
5697: a2 04                        ldx     #$04              ;no, cap it at 4
5699: e4 8d        :Lt4            cpx     wave_icbm_count   ;is it >= the number of ICBMs left in this wave?
569b: 90 02                        bcc     :LtWave           ;no, branch
569d: a6 8d                        ldx     wave_icbm_count   ;yes, cap to pending ICBM count
569f: e6 de        :LtWave         inc     atk_create_ctr    ;increment the attack counter
56a1: e4 de                        cpx     atk_create_ctr    ;compare to max we're allowed to create
56a3: 90 02                        bcc     :LtAtk            ;branch if less
56a5: a6 de                        ldx     atk_create_ctr    ;it's more, use the lower value instead
56a7: ca           :LtAtk          dex                       ;undo the earlier inc
56a8: 86 de        :SetAtk         stx     atk_create_ctr    ;set attack count
56aa: a6 de                        ldx     atk_create_ctr    ;check value
56ac: 30 33                        bmi     :Return           ;if < 0, bail
                   ; Check to see if the flier can fire.
56ae: a5 a7                        lda     wave_num          ;get wave number
56b0: c9 02                        cmp     #$02              ;on wave 1?
56b2: 90 1d                        bcc     :NoFlier          ;yes, branch (note: flier can't exist here)
56b4: ad 6d 01                     lda     flier_cur_y       ;is there an active flier?
56b7: f0 18                        beq     :NoFlier          ;no, branch
56b9: a5 c9                        lda     flier_fire_ctr    ;has the pre-fire delay interval passed?
56bb: c5 e1                        cmp     flier_fire_delay
56bd: 90 12                        bcc     :NoFlier          ;not yet, branch
                   ; 
56bf: ad 38 01                     lda     plane_cur_x       ;get horizontal position
56c2: c9 30                        cmp     #48               ;too far left?
56c4: 90 0b                        bcc     :NoFlier
56c6: c9 d0                        cmp     #208              ;too far right?
56c8: b0 07                        bcs     :NoFlier
56ca: a9 00                        lda     #$00
56cc: 85 c9                        sta     flier_fire_ctr    ;reset the firing interval counter
56ce: 4c 39 57                     jmp     FlierFire         ;fire one or more missiles from flier

                   ; Check to see if an ICBM can split.
56d1: a5 a7        :NoFlier        lda     wave_num          ;get wave number
56d3: c9 01                        cmp     #$01              ;is it < 1? (bug? should probably be #$02)
56d5: 90 07                        bcc     Jmp_CreateIcbms   ;yes, no MIRV
56d7: a4 c2                        ldy     mirv_slot         ;have we identified a MIRV candidate?
56d9: 30 03                        bmi     Jmp_CreateIcbms   ;no, branch
56db: 4c 3b 57                     jmp     SplitMirv         ;create some new missiles

56de: 4c 31 57     Jmp_CreateIcbms jmp     CreateIcbms       ;create ICBMs at the top of the screen

56e1: 60           :Return         rts

                   ; 
                   ; Creates a new ICBM or smartbomb at the top of the screen.  They always start
                   ; at the same row, right below the scores, but the horizontal position is
                   ; completely random.
                   ; 
                   ; On exit:
                   ;   (X-reg preserved)
                   ; 
                   ]saved_x        .var    $ac    {addr/1}

56e2: 86 ac        DoCreateIcbm    stx     ]saved_x          ;preserve X-reg
56e4: 20 82 57                     jsr     FindFreeIcbmSlot  ;find first free slot
56e7: a6 97                        ldx     slot_index        ;get ICBM slot index (0-7)
56e9: 30 29                        bmi     :RxReturn         ;bail if negative (can this happen?)
56eb: a9 de                        lda     #222              ;near top of screen, below scores
56ed: 9d 65 01                     sta     icbm_cur_y,x      ;set as current Y coordinate
56f0: 9d 9a 01                     sta     icbm_start_y_arr,x ; and as initial Y coordinate
                   ; 
56f3: ad 0a 40                     lda     POKEY_RANDOM      ;get random value (0-255)
56f6: a2 07                        ldx     #$07              ;check all 8 ICBM slots (even the empty ones?)
56f8: dd 8a 01     :DupLoop        cmp     icbm_start_x_arr,x ;did another ICBM start at this position?
56fb: d0 05                        bne     :NoMatch          ;not this one; branch
56fd: ad 0a 40                     lda     POKEY_RANDOM      ;get a new random value
5700: a2 08                        ldx     #$08              ;restart the search
5702: ca           :NoMatch        dex
5703: 10 f3                        bpl     :DupLoop
                   ; 
5705: a6 97                        ldx     slot_index        ;get ICBM slot index (0-7)
5707: 9d 8a 01                     sta     icbm_start_x_arr,x ;save random value as initial position
570a: 9d 30 01                     sta     icbm_cur_x,x      ; and as current position
570d: 20 91 57                     jsr     SetIcbmTarget     ;pick a city or silo
5710: a9 ff                        lda     #$ff
5712: 85 be                        sta     incoming_max_y    ;reset max ICBM altitude
5714: a6 ac        :RxReturn       ldx     ]saved_x          ;restore X-reg
5716: 60                           rts

                   ; 
                   ; Creates a smartbomb.  We create an ICBM, set the "is smartbomb" flag, and then
                   ; fix up the counters.
                   ; 
5717: 20 e2 56     CreateSmartBomb jsr     DoCreateIcbm      ;create ICBM
571a: a6 97                        ldx     slot_index        ;check the slot index (0-15)
571c: 30 12                        bmi     :Return           ;didn't work, bail
571e: 20 d1 7a                     jsr     EnableBombSound
5721: e6 da                        inc     num_live_bombs    ;increment the number of active smartbombs
5723: c6 db                        dec     wave_sbomb_count  ;decrement the number of pending smartbombs
5725: e6 8d                        inc     wave_icbm_count   ;fix pending ICBM count
5727: c6 8c                        dec     num_live_icbms    ;fix live ICBM count
5729: a5 d9                        lda     smartbomb_mask    ;get the smartbomb mask
572b: 1d ef 60                     ora     single_bits-8,x   ;set the bit for this entry
572e: 85 d9                        sta     smartbomb_mask    ;save it
5730: 60           :Return         rts

                   ; 
                   ; Creates one or more ICBMs at the top of the screen.
                   ; 
5731: 20 e2 56     CreateIcbms     jsr     DoCreateIcbm      ;create ICBM
5734: c6 de                        dec     atk_create_ctr    ;decrement count
5736: 10 f9                        bpl     CreateIcbms       ;if not done, loop
5738: 60                           rts

                   ; 
                   ; Launches one or more ICBMs from the flier.
                   ; 
                   ]icbm_slot      .var    $ac    {addr/1}

5739: a0 08        FlierFire       ldy     #$08              ;flier is in 9th ICBM slot
                   ; 
                   ; Splits a missile.  We create up to 3 additional missiles.
                   ; 
                   ; On entry:
                   ;   Y-reg: "source" ICBM slot index (0-7) or flier index (8)
                   ;   $de: number of missiles to create, minus one
                   ; 
573b: a5 de        SplitMirv       lda     atk_create_ctr    ;get number of things to create
573d: c9 02                        cmp     #$02              ;is it 1?
573f: 90 02                        bcc     :Lt2              ;yes, branch
5741: a9 02                        lda     #$02              ;no, cap it at 2 (which means 3 new ICBMs)
5743: 85 de        :Lt2            sta     atk_create_ctr    ;update counter
5745: 84 ac                        sty     ]icbm_slot        ;save "source" ICBM slot (0-8)
                   ; Find the first free ICBM slot.  If nothing is free, $97 is unmodified.  We
                   ; test the value here, but haven't explicitly set it to anything, so it's
                   ; unclear whether the BMI that follows will ever be taken.  (Also, the BMI is
                   ; branching to a BPL that will fall through, rather than the RTS that follows...
                   ; perhaps there was some conditionally-assembled code there in the source?)
5747: 20 82 57     :Loop           jsr     FindFreeIcbmSlot  ;find a free ICBM slot (0-7)
574a: a6 97                        ldx     slot_index        ;get result
574c: 30 31                        bmi     :BplLoop          ;if it's negative, bail
                   ; 
574e: a4 ac                        ldy     ]icbm_slot        ;get "source" ICBM slot (0-8)
5750: a9 00                        lda     #$00              ;default offset to zero
                   ; This appears to be trying to offset the missile launch location by +/-4, based
                   ; on the flier's movement direction and screen orientation.  The code doesn't
                   ; seem to get executed though, because the initial test is wrong.
5752: c0 09                        cpy     #$09              ;(bug? should be #$08?)
5754: d0 0b                        bne     :NotFlier         ;not flier, branch
5756: a5 c8                        lda     flier_move_dir    ;moving to the right?
5758: 10 05                        bpl     :FlyRight         ;yes, branch
575a: a9 fc                        lda     #$fc              ;no, start at X-4
575c: b8                           clv
575d: 50 02                        bvc     :NotFlier

575f: a9 04        :FlyRight       lda     #$04              ;start at X+4
5761: 24 fc        :NotFlier       bit     cocktail_flip     ;are we flipped?
5763: 50 02                        bvc     :NoFlip           ;no, branch
5765: 49 ff                        eor     #$ff              ;negate the offset (not 2's complement?)
                   ; 
5767: 18           :NoFlip         clc
5768: 79 30 01                     adc     icbm_cur_x,y      ;add the offset to the X coord of the source
576b: 9d 30 01                     sta     icbm_cur_x,x      ;set X coord of new missile
576e: 9d 8a 01                     sta     icbm_start_x_arr,x
5771: b9 65 01                     lda     icbm_cur_y,y      ;get Y coord of source
5774: 9d 65 01                     sta     icbm_cur_y,x      ;set Y coord of new missile
5777: 9d 9a 01                     sta     icbm_start_y_arr,x
577a: 20 91 57                     jsr     SetIcbmTarget     ;pick a destination and set it
577d: c6 de                        dec     atk_create_ctr    ;have we created all new items?
577f: 10 c6        :BplLoop        bpl     :Loop             ;not yet, branch
5781: 60                           rts

                   ; 
                   ; Finds the first free ICBM slot.
                   ; 
                   ; The callers react to $97 being negative, as if that were expected to mean that
                   ; no free slot was found.  However, that can't happen unless the caller
                   ; initializes $97 to a negative value, which they don't do.  (Bug? Could be
                   ; fixed by swapping the order of BNE and STX.)
                   ; 
                   ; On exit:
                   ;   $97: ICBM slot (0-7) if one was found, unmodified if not
                   ; 
                   FindFreeIcbmSlot
5782: a2 07                        ldx     #$07
5784: bd 65 01     :Loop           lda     icbm_cur_y,x      ;check current Y position
5787: d0 04                        bne     :InUse            ;not zero, slot in use; branch
5789: 86 97                        stx     slot_index        ;save index
578b: a2 00                        ldx     #$00              ;ensure BPL falls through
578d: ca           :InUse          dex
578e: 10 f4                        bpl     :Loop             ;if we haven't found a slot, loop
5790: 60                           rts

                   ; 
                   ; Chooses a city or silo target for an ICBM or smartbomb.  Sets the target
                   ; coordinates in the specified ICBM slot, then sets up the path parameters.
                   ; 
                   ; On entry:
                   ;   $97: ICBM slot index (0-7)
                   ; 
                   ]untarg_city    .var    $98    {addr/1}
                   ]untarg_silo    .var    $99    {addr/1}

5791: a6 b9        SetIcbmTarget   ldx     cur_plyr_num      ;get current player number (0/1)
5793: b5 c5                        lda     live_city_mask,x  ;get bit map of live cites
5795: 45 cb                        eor     city_targ_flags   ;remove cities that have been targeted
5797: 35 c5                        and     live_city_mask,x  ;strip out cities that were targeted but are now dead
5799: 85 98                        sta     ]untarg_city      ;save map of untargeted cities
579b: a5 cd                        lda     live_silo_mask    ;get bit map of live silos
579d: 45 cc                        eor     silo_targ_flags   ;remove silos that have been targeted
579f: 25 cd                        and     live_silo_mask    ;strip out silos that were targeted but are now dead
57a1: 85 99                        sta     ]untarg_silo      ;save map of untargeted silos
                   ; Add the number of cities destroyed in this wave to the number of targeted-but-
                   ; live cities.  If it's >= 3, aim at a silo instead.  This ensures that we never
                   ; destroy more than 3 cities in a wave.
57a3: a4 cf                        ldy     cities_destroyed  ;get number of cities destroyed so far this wave
57a5: a5 cb                        lda     city_targ_flags   ;get city target flags
57a7: 35 c5                        and     live_city_mask,x  ;remove dead cities
57a9: 10 01        :Loop           bpl     :HiClear          ;high bit not set, branch
57ab: c8                           iny                       ;increment count
57ac: 0a           :HiClear        asl     A                 ;shift next bit into high bit
57ad: d0 fa                        bne     :Loop             ;branch if cities remain
57af: c0 03                        cpy     #$03              ;is it >= 3?
57b1: b0 1e                        bcs     :AimAtSilo        ;yes, aim at a silo instead
                   ; 
57b3: a0 00                        ldy     #$00              ;pick a city (0+)
57b5: a5 98                        lda     ]untarg_city      ;get bit map of untargeted cities
57b7: f0 06                        beq     :RndSilo          ;no untargeted cities, branch to shoot silo instead
57b9: 20 1d 58                     jsr     PickRndBit        ;pick an untargeted city at random
57bc: b8                           clv
57bd: 50 0f                        bvc     :Br_SetTarget     ;(always)

57bf: a0 06        :RndSilo        ldy     #$06              ;pick a silo (6+)
57c1: a5 99                        lda     ]untarg_silo      ;get bit map of untargeted silos
57c3: f0 06                        beq     :RndAny           ;no untargeted silos, branch to pick anything
57c5: 20 1d 58                     jsr     PickRndBit        ;pick an untargeted silo at random
57c8: b8                           clv
57c9: 50 03                        bvc     :Br_SetTarget     ;(always)

57cb: 20 0c 58     :RndAny         jsr     GetRnd9           ;pick any city or silo, alive or dead
57ce: b8           :Br_SetTarget   clv
57cf: 50 0f                        bvc     :SetTarget        ;(always)

57d1: a0 06        :AimAtSilo      ldy     #$06              ;pick a silo (6+)
57d3: a5 99                        lda     ]untarg_silo      ;get bit map of untargeted silos
57d5: f0 06                        beq     :DoubleCity       ;no untargeted silos, double up on a city instead
57d7: 20 1d 58                     jsr     PickRndBit        ;pick an untargeted silo at random
57da: b8                           clv
57db: 50 03                        bvc     :SetTarget        ;(always)

57dd: 20 17 58     :DoubleCity     jsr     PickRndTargCity   ;pick an already-targeted city
                   ; 
                   ; ICBM slot index (0-7) in $97, target city or silo in Y-reg (0-8).
57e0: a9 ff        :SetTarget      lda     #$ff
57e2: 20 47 58                     jsr     UpdateTargetFlag  ;update target flag bits for cities or silos
57e5: a6 97                        ldx     slot_index        ;get ICBM slot index (0-7)
57e7: b9 e2 60                     lda     city_x_tbl,y      ;get target's horizontal position
57ea: 9d aa 01                     sta     icbm_targ_x_arr,x ;set target X coord
57ed: b9 eb 60                     lda     city_y_tbl,y      ;get target's vertical position
57f0: 9d ba 01                     sta     icbm_targ_y_arr,x ;set target Y coord
57f3: a9 00                        lda     #$00
57f5: 9d 20 01                     sta     icbm_cur_xfrac,x  ;set fractional part to zero
57f8: 9d 55 01                     sta     icbm_cur_yfrac,x
57fb: bd f7 60                     lda     single_bits,x     ;get bit for this slot
57fe: 49 ff                        eor     #$ff              ;invert to form an AND mask
5800: 25 d9                        and     smartbomb_mask    ;mark it as not a smartbomb
5802: 85 d9                        sta     smartbomb_mask    ;(this will be overridden later if it is a bomb)
5804: 20 fa 58                     jsr     CalcIcbmPath      ;set up the path to the target
5807: c6 8d                        dec     wave_icbm_count   ;decrement the number of pending ICBMs
5809: e6 8c                        inc     num_live_icbms    ;increment the number of active ICBMs
580b: 60                           rts                       ;(counters will be adjusted later for smartbombs)

                   ; 
                   ; Returns a random number in the range [0,8].  Used to pick a ground target (6
                   ; cities, 3 silos).
                   ; 
                   ; Values 1-7 are more likely (1 in 8) than 0 or 8 (1 in 16 each).
                   ; 
                   ; On exit:
                   ;   Y-reg: random value [0,8]
                   ; 
580c: ad 0a 40     GetRnd9         lda     POKEY_RANDOM      ;get random number
580f: 4a                           lsr     A                 ;shift low bit into carry
5810: 29 07                        and     #$07              ;reduce to 0-7
5812: a8                           tay
5813: 90 01                        bcc     :Return           ;if the carry was clear, return
5815: c8                           iny                       ;add one; max value now 8
5816: 60           :Return         rts

                   ; 
                   ; Inverts bit mask, then falls through to PickRndBit.
                   ; 
                   ; Used to pick an already-targeted city.
                   ; 
                   ; On entry:
                   ;   $98: bit map of untargeted cities (must not be $ff)
                   ; 
                   ]untarg_city    .var    $98    {addr/1}

5817: a0 00        PickRndTargCity ldy     #$00              ;set initial value to zero
5819: a5 98                        lda     ]untarg_city      ;get mask
581b: 49 ff                        eor     #$ff              ;invert bits
                   ; 
                   ; Given a byte with 1 or more bits set, picks a bit at random.
                   ; 
                   ; This is used to pick a city or silo from a bit map, to attack or reconstruct.
                   ; 
                   ; NOTE: if A-reg is zero, this will hang.
                   ; 
                   ; On entry:
                   ;   Y-reg: initial value (e.g. $00 for cities, $06 for silos)
                   ;   A-reg: bit mask
                   ; 
                   ; On exit:
                   ;   Y-reg: initial value + bit index
                   ; 
                   ]num_set        .var    $98    {addr/1}

581d: 48           PickRndBit      pha                       ;preserve bit mask
581e: a2 00                        ldx     #$00              ;init count
5820: 29 ff        :Loop1          and     #$ff              ;no-op to set flags for A-reg
5822: 10 01                        bpl     :NotSet           ;branch if high bit not set
5824: e8                           inx                       ;increment count
5825: 0a           :NotSet         asl     A                 ;shift high bit out
5826: d0 f8                        bne     :Loop1            ;loop if there are more bits
5828: 86 98                        stx     ]num_set          ;save number of set bits
                   ; We found N set bits.  Generate a random value [0,N-1].
582a: ad 0a 40     :Loop2          lda     POKEY_RANDOM      ;get a random number
582d: 3d 3e 58                     and     :bit_masks-1,x    ;strip off bits outside valid range
5830: c5 98                        cmp     ]num_set          ;is it >= the number of bits we found?
5832: b0 f6                        bcs     :Loop2            ;yes, try again
                   ; We want the Nth set bit.  Figure out which bit number that is.
5834: aa                           tax                       ;use random value as counter
5835: 68                           pla                       ;restore bit mask
5836: 88                           dey                       ;decr so first incr is zero
5837: 0a           :Loop3          asl     A                 ;shift high bit into carry
5838: c8                           iny                       ;increment counter
5839: 90 fc                        bcc     :Loop3            ;not set, loop
583b: ca                           dex                       ;found set bit, decrement rnd val
583c: 10 f9                        bpl     :Loop3            ;if not at the bit we want, loop
583e: 60                           rts                       ;result in Y-reg

583f: 00 01 03 03+ :bit_masks      .bulk   $00,$01,$03,$03,$07,$07,$07,$07 ;masks for roughly limiting the random number

                   ; 
                   ; Sets or clears a target flag.  This lets the game see which sites have been
                   ; targeted already.
                   ; 
                   ; On entry:
                   ;   Y-reg: target index: 0-5 city, 6-8 silo
                   ;   A-reg: < $80 to clear bit, >= $80 to set bit
                   ; 
                   ; On exit:
                   ;   Y-reg: preserved
                   ; 
                   • Clear variables
                   ]temp           .var    $98    {addr/1}
                   ]saved_y        .var    $99    {addr/1}

                   UpdateTargetFlag
5847: 85 98                        sta     ]temp
5849: 84 99                        sty     ]saved_y          ;preserve Y-reg
584b: a2 00                        ldx     #$00              ;assume city byte
584d: 98                           tya
584e: c9 06                        cmp     #$06              ;is it 0-5?
5850: 90 06                        bcc     :CityByte         ;yes, update the city byte
5852: 38                           sec
5853: e9 06                        sbc     #$06              ;reduce 6-8 to 0-2
5855: a8                           tay
5856: a2 01                        ldx     #$01              ;update the silo byte
                   ; 
5858: b9 f7 60     :CityByte       lda     single_bits,y     ;get bit for index
585b: 49 ff                        eor     #$ff              ;invert to form AND mask
585d: 35 cb                        and     city_targ_flags,x ;clear flag
585f: 24 98                        bit     ]temp             ;is high bit of arg set?
5861: 10 03                        bpl     :ClearBit         ;no, leave bit clear
5863: 19 f7 60                     ora     single_bits,y     ;yes, set the bit
5866: 95 cb        :ClearBit       sta     city_targ_flags,x ;save result
5868: a4 99                        ldy     ]saved_y          ;restore Y-reg
586a: 60                           rts

                   ; 
                   ; Creates an explosion for an ABM, ICBM, or smartbomb.
                   ; 
                   ; On entry:
                   ;   $97: slot of thing that exploded (0-15)
                   ; 
                   ; On exit:
                   ;   (X-reg preserved)
                   ; 
                   ]saved_x        .var    $ab    {addr/1}

586b: 86 ab        CreateExplosion stx     ]saved_x          ;preserve X-reg
586d: a6 97                        ldx     slot_index        ;get slot index for exploding item
586f: bd 5d 01                     lda     abm_cur_y,x       ;check current Y coordinate
5872: c9 d2                        cmp     #210              ;at or above line 210?
5874: b0 2a                        bcs     :RetireOrdnance   ;yes, could overwrite score; don't show
                   ; 
5876: a4 96                        ldy     next_expl_slot    ;get next available explosion slot
5878: c6 96                        dec     next_expl_slot    ;update slot number
587a: 10 04                        bpl     :SlotOk           ;not negative, branch
587c: a9 13                        lda     #19               ;reset slot; total of 20 slots available
587e: 85 96                        sta     next_expl_slot
                   ; 
5880: e6 8e        :SlotOk         inc     num_explosions    ;increment the number of active explosions
5882: bd 28 01                     lda     abm_cur_x,x       ;copy X coordinate to explosion table
5885: 99 39 01                     sta     expl_xpos,y
5888: bd 5d 01                     lda     abm_cur_y,x       ;copy Y coordinate
588b: 99 6e 01                     sta     expl_ypos,y
588e: e0 08                        cpx     #$08              ;slot 0-7 (ABM)?
5890: 90 07                        bcc     :Abm              ;yes, branch
5892: e6 90                        inc     num_expl_icbms    ;no, increment number of ICBM explosions
5894: a9 80                        lda     #$80              ;set high bit on explosion radius index value
5896: b8                           clv
5897: 50 04                        bvc     :SetRadius        ;(always)

5899: e6 8f        :Abm            inc     num_expl_abms     ;increment number of ABM explosions
589b: a9 00                        lda     #$00              ;leave high bit clear on explosion radius index
                   ; 
589d: 99 c2 01     :SetRadius      sta     expl_radius_idx_arr,y ;set index into explosion radius array
58a0: a9 00        :RetireOrdnance lda     #$00              ;send the abm, missile, or smartbomb to a nice
58a2: 9d 5d 01                     sta     abm_cur_y,x       ; farm upstate
58a5: 9d 28 01                     sta     abm_cur_x,x
58a8: a6 ab                        ldx     ]saved_x          ;restore X-reg
58aa: 60                           rts

                   ; 
                   ; Checks to see if the distance between two points is less than or equal to a
                   ; certain distance.  Returns with the carry clear if the distance between the
                   ; two points is less than the distance argument.  Used for collision testing.
                   ; 
                   ; The distance between two objects can be approximated with:
                   ;   distance = 0.41 * dx + 0.941246 * dy
                   ;   (for dy > dx; flip the values if dy < dx)
                   ; We then approximate that further:
                   ;   distance = 0.375 * dx + 1.0 * dy
                   ; This is easy because 0.375 is 3/8.
                   ; 
                   ; On entry:
                   ;   $9b/9c: X/Y coordinate #1
                   ;   $9d/9e: X/Y coordinate #2
                   ;   $9a: distance to test
                   ; 
                   ; On exit:
                   ;   C-flag: result of calc_dist CMP arg, i.e. clear if collision
                   ; 
                   • Clear variables
                   ]small_part     .var    $98    {addr/1}
                   ]large_part     .var    $99    {addr/1}
                   ]dist           .var    $9a    {addr/1}
                   ]xc_1           .var    $9b    {addr/1}
                   ]yc_1           .var    $9c    {addr/1}
                   ]xc_2           .var    $9d    {addr/1}
                   ]yc_2           .var    $9e    {addr/1}
                   ]deltaY         .var    $b1    {addr/1}

58ab: a5 9e        CheckDist       lda     ]yc_2             ;get Y2
58ad: c5 9c                        cmp     ]yc_1             ;less than Y1?
58af: 90 05                        bcc     :Y2LtY1           ;yes, branch
58b1: e5 9c                        sbc     ]yc_1             ;no, compute Y2-Y1
58b3: b8                           clv
58b4: 50 05                        bvc     :ChkDeltaY

58b6: a5 9c        :Y2LtY1         lda     ]yc_1             ;compute Y1-Y2
58b8: 38                           sec
58b9: e5 9e                        sbc     ]yc_2
58bb: c5 9a        :ChkDeltaY      cmp     ]dist             ;is deltaY >= dist?
58bd: b0 36                        bcs     :Return           ;yes, collision not possible; bail
58bf: 85 b1                        sta     ]deltaY
                   ; 
58c1: a5 9d                        lda     ]xc_2             ;get X2
58c3: c5 9b                        cmp     ]xc_1             ;less than X1?
58c5: 90 05                        bcc     :X2LtX1           ;yes, branch
58c7: e5 9b                        sbc     ]xc_1             ;no, compute X2-X1
58c9: b8                           clv
58ca: 50 05                        bvc     :ChkDeltaX

58cc: a5 9b        :X2LtX1         lda     ]xc_1             ;compute X1-X2
58ce: 38                           sec
58cf: e5 9d                        sbc     ]xc_2
58d1: c5 9a        :ChkDeltaX      cmp     ]dist             ;is deltaX >= dist?
58d3: b0 20                        bcs     :Return           ;yes, collision not possible; bail
                   ; 
58d5: c5 b1                        cmp     ]deltaY           ;is deltaX >= deltaY?
58d7: b0 09                        bcs     :DeltaXBig        ;yes, branch
58d9: 85 98                        sta     ]small_part       ;no, deltaY is larger
58db: a5 b1                        lda     ]deltaY
58dd: 85 99                        sta     ]large_part
58df: b8                           clv
58e0: 50 06                        bvc     :DoCalc

58e2: 85 99        :DeltaXBig      sta     ]large_part       ;deltaX is larger
58e4: a5 b1                        lda     ]deltaY
58e6: 85 98                        sta     ]small_part
                   ; 
58e8: a5 98        :DoCalc         lda     ]small_part       ;get small part
58ea: 4a                           lsr     A                 ;divide by 2
58eb: 18                           clc
58ec: 65 98                        adc     ]small_part       ;add to itself to get 3/2
58ee: 4a                           lsr     A                 ;divide by 4 to get 3/8
58ef: 4a                           lsr     A
58f0: 18                           clc
58f1: 65 99                        adc     ]large_part       ;add to large part
58f3: c5 9a                        cmp     ]dist             ;compare to distance arg
58f5: 60           :Return         rts                       ;return with carry flag conditioned appropriately

                   ; 
                   ; Sets up the math for a missile.
                   ; 
                   ; (Note: 2 out of 3 call sites do an LDY with the ABM slot index immediately
                   ; before calling here, which conditions the 'N' flag and makes the STY
                   ; redundant.  The call from $52ed doesn't do this, but the flag happens to be
                   ; conditioned by restoring the X-reg, which holds a value from 0-2.  So the BPL
                   ; at $58f8 appears to be a branch-always.)
                   ; 
                   ; On entry:
                   ;   Y-reg: ABM/ICBM slot index (0-15)
                   ;   N flag: clear
                   ; 
                   ; On exit:
                   ;   $b0-b1: deltaX/deltaY
                   ;   (X/Y-reg preserved)
                   ; 
58f6: 84 97        CalcMissilePath sty     slot_index
58f8: 10 06                        bpl     DoMissilePath     ;(always?)
                   ; 
                   ; Sets up the math for an ICBM.  Adds 8 to the ICBM index to form a general slot
                   ; index.
                   ; 
                   ; On entry:
                   ;   X-reg: ICBM index (0-7)
                   ; 
                   ; On exit:
                   ;   (X/Y-reg preserved)
                   ; 
58fa: 8a           CalcIcbmPath    txa
58fb: 18                           clc
58fc: 69 08                        adc     #$08
58fe: 85 97                        sta     slot_index
                   ; 
                   ; Sets up the path parameters for a missile.
                   ; 
                   ; The start and end positions have already been set.  This routine sets up the
                   ; values that determine how the missile gets from one place to the other.
                   ; 
                   ; On entry:
                   ;   $97: missile slot index (0-15)
                   ; 
                   ; On exit:
                   ;   $b0-b1: deltaX/deltaY
                   ;   (X/Y-reg preserved)
                   ; 
                   • Clear variables
                   ]small_axis     .var    $98    {addr/1}
                   ]big_axis       .var    $99    {addr/1}
                   ]result_int     .var    $a9    {addr/1}
                   ]result_frac    .var    $aa    {addr/1}
                   ]deltaX         .var    $b0    {addr/1}
                   ]deltaY         .var    $b1    {addr/1}

5900: 8a           DoMissilePath   txa
5901: 48                           pha                       ;preserve X-reg
5902: 98                           tya
5903: 48                           pha                       ;preserve Y-reg
                   ; Compute absolute value of deltaX and deltaY.
5904: a6 97                        ldx     slot_index        ;get missile slot index
5906: bd b2 01                     lda     abm_targ_y_arr,x  ;get target Y coord
5909: dd 5d 01                     cmp     abm_cur_y,x       ;compare to initial Y coord
590c: 90 06                        bcc     :TargYSmaller
590e: fd 5d 01                     sbc     abm_cur_y,x       ;compute diff
5911: b8                           clv
5912: 50 07                        bvc     :SetDeltaY        ;(always)

5914: bd 5d 01     :TargYSmaller   lda     abm_cur_y,x       ;get initial position
5917: 38                           sec
5918: fd b2 01                     sbc     abm_targ_y_arr,x  ;subtract target position
591b: 85 b1        :SetDeltaY      sta     ]deltaY           ;save absolute value of delta Y
                   ; 
591d: bd a2 01                     lda     abm_targ_x_arr,x  ;get target X coord
5920: dd 28 01                     cmp     abm_cur_x,x       ;compare to initial X coord
5923: 90 06                        bcc     :TargXSmaller     ;target X smaller, branch
5925: fd 28 01                     sbc     abm_cur_x,x
5928: b8                           clv
5929: 50 07                        bvc     :SetDeltaX        ;(always)

592b: bd 28 01     :TargXSmaller   lda     abm_cur_x,x       ;get initial position
592e: 38                           sec
592f: fd a2 01                     sbc     abm_targ_x_arr,x  ;subtract target position
5932: 85 b0        :SetDeltaX      sta     ]deltaX           ;save absolute value of delta X
                   ; Determine which is the "big" axis and which is the "small" axis.
5934: a5 b0                        lda     ]deltaX           ;get deltaX (redundant?)
5936: c5 b1                        cmp     ]deltaY           ;is it >= deltaY?
5938: b0 09                        bcs     :XBigger          ;yes, branch
593a: 85 98                        sta     ]small_axis       ;X is small
593c: a5 b1                        lda     ]deltaY
593e: 85 99                        sta     ]big_axis         ;Y is large
5940: b8                           clv
5941: 50 06                        bvc     :GotBigSmall

5943: 85 99        :XBigger        sta     ]big_axis         ;X is large
5945: a5 b1                        lda     ]deltaY
5947: 85 98                        sta     ]small_axis       ;Y is small
                   ; Compute an approximation of the length of the line.  This uses the same "large
                   ; + 3/8 * small" formula as at $58ab.  ICBM corner shots can exceed 255 (e.g.
                   ; $b5,$de to $14,$16 is 260), so to keep it in 8 bits we limit the result.
5949: a5 98        :GotBigSmall    lda     ]small_axis       ;get small axis delta
594b: 4a                           lsr     A                 ;divide by 2
594c: 18                           clc
594d: 65 98                        adc     ]small_axis       ;add to itself to get 3/2
594f: 4a                           lsr     A                 ;divide by 4 to get 3/8
5950: 4a                           lsr     A
5951: 18                           clc
5952: 65 99                        adc     ]big_axis         ;add the major axis
5954: 90 02                        bcc     :NoOvfl           ;branch if it fit in 8 bits
5956: a9 ff                        lda     #$ff              ;cap at 255
                   ]distance       .var    $98    {addr/1}

5958: 85 98        :NoOvfl         sta     ]distance         ;store in ZP
                   ; Compute increments for X and Y that will advance the missile 1 unit toward its
                   ; destination.  If the missile's path is 200 units long, it will reach its
                   ; target in 200 frames.  Values are stored as 8.8 fixed-point.  Because the
                   ; distance is approximate, the line may not intersect the actual target pixel.
595a: a4 98                        ldy     ]distance
595c: a5 b1                        lda     ]deltaY
595e: 20 bb 59                     jsr     Divide8_8         ;compute (deltaY / distance)
5961: a5 a9                        lda     ]result_int       ;save result
5963: 9d 30 06                     sta     msl_inc_yint,x
5966: a5 aa                        lda     ]result_frac
5968: 9d 20 06                     sta     msl_inc_yfrac,x
                   ; 
596b: a4 98                        ldy     ]distance
596d: a5 b0                        lda     ]deltaX
596f: 20 bb 59                     jsr     Divide8_8         ;compute (deltaX / distance)
5972: a5 a9                        lda     ]result_int       ;save result
5974: 9d 10 06                     sta     msl_inc_xint,x
5977: a5 aa                        lda     ]result_frac
5979: 9d 00 06                     sta     msl_inc_xfrac,x
                   ; The above was computed with absolute values.  Negate values as needed.
597c: bd b2 01                     lda     abm_targ_y_arr,x  ;get target Y coord
597f: dd 5d 01                     cmp     abm_cur_y,x       ;is it >= the initial Y coord?
5982: b0 15                        bcs     :YOkay            ;yes, branch
5984: bd 20 06                     lda     msl_inc_yfrac,x   ;no, invert the 8.8 value (2's complement)
5987: 49 ff                        eor     #$ff
5989: 18                           clc
598a: 69 01                        adc     #$01
598c: 9d 20 06                     sta     msl_inc_yfrac,x
598f: bd 30 06                     lda     msl_inc_yint,x    ;high byte too
5992: 49 ff                        eor     #$ff
5994: 69 00                        adc     #$00
5996: 9d 30 06                     sta     msl_inc_yint,x
                   ; 
5999: bd a2 01     :YOkay          lda     abm_targ_x_arr,x  ;get target X coord
599c: dd 28 01                     cmp     abm_cur_x,x       ;is it >= the initial Y coord?
599f: b0 15                        bcs     :Done             ;yes, branch
59a1: bd 00 06                     lda     msl_inc_xfrac,x   ;no, invert the 8.8 value (2's complement)
59a4: 49 ff                        eor     #$ff
59a6: 18                           clc
59a7: 69 01                        adc     #$01
59a9: 9d 00 06                     sta     msl_inc_xfrac,x
59ac: bd 10 06                     lda     msl_inc_xint,x    ;high byte too
59af: 49 ff                        eor     #$ff
59b1: 69 00                        adc     #$00
59b3: 9d 10 06                     sta     msl_inc_xint,x
                   ; 
59b6: 68           :Done           pla
59b7: a8                           tay                       ;restore Y-reg
59b8: 68                           pla
59b9: aa                           tax                       ;restore X-reg
59ba: 60                           rts

                   ; 
                   ; Divides an 8-bit value by an 8-bit value, yielding an 8.8 result.
                   ; 
                   ; For our uses the denominator is always equal or larger, so the integer part of
                   ; the quotient should always be one or zero.
                   ; 
                   ; On entry:
                   ;   A-reg: numerator (deltaX or deltaY)
                   ;   Y-reg: denominator (distance)
                   ; 
                   ; On exit:
                   ;   $a9/aa: 8.8 result
                   ; 
                   ]work_bits      .var    $a8    {addr/1}
                   ]saved_x        .var    $ab    {addr/1}
                   ]denom          .var    $ad    {addr/1}

59bb: 84 ad        Divide8_8       sty     ]denom            ;store denominator in ZP
59bd: 86 ab                        stx     ]saved_x          ;preserve X-reg
59bf: 20 d0 59                     jsr     :CalcQuot         ;calculate integer quotient
59c2: 86 a9                        stx     ]result_int       ;save integer part; note work bits are still in A-reg
59c4: a2 00                        ldx     #$00              ;treat numerator as 16-bit value by
59c6: 86 a8                        stx     ]work_bits        ; sign-extending with zero
59c8: 20 d4 59                     jsr     :CalcRem          ;calculate fractional part
59cb: 86 aa                        stx     ]result_frac      ;save fractional part
59cd: a6 ab                        ldx     ]saved_x          ;restore X-reg
59cf: 60                           rts

59d0: 85 a8        :CalcQuot       sta     ]work_bits        ;init to numerator
59d2: a9 00                        lda     #$00              ;init A-reg to zero
59d4: a0 07        :CalcRem        ldy     #$07              ;8 bits
59d6: 26 a8        :Loop1          rol     ]work_bits        ;roll carry into low, roll high bit into accumulator,
59d8: 2a                           rol     A                 ; roll high bit into carry
59d9: 90 05                        bcc     :NoSub1
59db: e5 ad                        sbc     ]denom            ;carry set, acc must be larger, subtract denom
59dd: 38                           sec
59de: b0 06                        bcs     :NoSub2           ;(always)

59e0: c5 ad        :NoSub1         cmp     ]denom            ;is acc larger than denominator?
59e2: 90 02                        bcc     :NoSub2           ;no, branch
59e4: e5 ad                        sbc     ]denom            ;yes, subtract denominator
59e6: 88           :NoSub2         dey                       ;done yet?
59e7: 10 ed                        bpl     :Loop1            ;no, loop
59e9: 26 a8                        rol     ]work_bits        ;roll last bit in
59eb: a6 a8                        ldx     ]work_bits        ;get as return value
59ed: 60                           rts

                   ; 
                   ; Checks to see if the wave is done.  In some cases we can run everything at
                   ; maximum speed, in others we can stop immediately.
                   ; 
                   • Clear variables
                   ]have_abms      .var    $99    {addr/1}

59ee: a4 b9        CheckWaveDone   ldy     cur_plyr_num      ;get current player number
59f0: a5 a0                        lda     abms_left         ;see if there are any ABMs left
59f2: 05 a1                        ora     abms_left+1
59f4: 05 a2                        ora     abms_left+2
59f6: 85 99                        sta     ]have_abms        ;save for later
59f8: d0 24                        bne     :StillFighting    ;if player has some ABMs, branch
                   ; 
59fa: b9 c5 00                     lda     live_city_mask,y  ;check if any cities remain
59fd: f0 08                        beq     :BneRarely        ;no, branch (to not-taken BNE)
59ff: a5 cf                        lda     cities_destroyed  ;see how many were destroyed this wave
5a01: c9 03                        cmp     #$03              ;< 3?
5a03: 90 02                        bcc     :BneRarely        ;yes, branch
5a05: a9 00                        lda     #$00              ;no, >= 3; load zero to ensure BNE is not taken
5a07: d0 15        :BneRarely      bne     :StillFighting    ;branch if 1 or 2 cities were destroyed
                   ; No ABMs, and no cities or 3+ cities destroyed this wave.  Have mercy.
5a09: a9 00                        lda     #$00
5a0b: 85 c9                        sta     flier_fire_ctr    ;restart flier firing counter
5a0d: 85 8d                        sta     wave_icbm_count   ;stop launching ICBMs and bombs
5a0f: 85 db                        sta     wave_sbomb_count
5a11: ad 6d 01                     lda     flier_cur_y       ;do we have a flier?
5a14: 05 bc                        ora     active_abm_count  ;or an in-flight ABM?
5a16: 05 8e                        ora     num_explosions    ;or an explosion?
5a18: d0 04                        bne     :StillFighting    ;yes, branch
5a1a: a9 08                        lda     #FN_END_WAVE      ;no, go straight to end-of-wave func, even if
5a1c: 85 91                        sta     func_index        ; bombs/missiles are incoming (they're harmless)
                   ; 
5a1e: a5 bc        :StillFighting  lda     active_abm_count  ;do we have an in-flight ABM?
5a20: 05 8e                        ora     num_explosions    ;or an explosion?
5a22: 05 99                        ora     ]have_abms        ;or unfired ABMs?
5a24: d0 04                        bne     :StaySlow         ;yes, branch
5a26: a9 ff                        lda     #$ff
5a28: 85 bf                        sta     full_speed_flag   ;set the "full speed" flag
5a2a: a5 bf        :StaySlow       lda     full_speed_flag   ;is "full speed" flag set?
5a2c: f0 0d                        beq     :CheckBusy        ;no, branch
                   ; 
5a2e: a9 00                        lda     #$00              ;zero out movement delays, so flier and
5a30: 85 c7                        sta     flier_move_ctr    ; incoming ordnance move at max speed
5a32: 85 b3                        sta     icbm_move_ctr+1
5a34: 85 b8                        sta     icbm_move_delay+1
5a36: 85 b7                        sta     icbm_move_delay
5a38: 20 72 65                     jsr     HideCrosshairs    ;hide the crosshairs
                   ; 
5a3b: a5 8d        :CheckBusy      lda     wave_icbm_count   ;are there pending ICBMs?
5a3d: 05 bc                        ora     active_abm_count  ;or in-flight ABMs?
5a3f: 05 db                        ora     wave_sbomb_count  ;or pending smartbombs?
5a41: 05 8c                        ora     num_live_icbms    ;or in-flight ICBMs?
5a43: 05 da                        ora     num_live_bombs    ;or in-flight smartbombs?
5a45: 0d 6d 01                     ora     flier_cur_y       ;or a flier?
5a48: 05 8e                        ora     num_explosions    ;or an explosion?
5a4a: d0 04                        bne     :Return           ;yes, branch
5a4c: a9 08                        lda     #FN_END_WAVE      ;no, this wave is 100% complete, so
5a4e: 85 91                        sta     func_index        ; go to end-of-wave func
5a50: 60           :Return         rts

5a51: 4d                           .dd1    $4d               ;checksum byte

                   ; 
                   ; Updates the flashing indicator arrows.
                   ; 
                   ; If a game is in progress, this draws the 1UP/2UP indicator next to the
                   ; player's score.  If a game is not in progress, and we're in the "play game"
                   ; phase of attract mode, this draws a downward facing arrow over each city.
                   ; 
                   • Clear variables
                   ]counter        .var    $ab    {addr/1}
                   ]color_tmp      .var    $b6    {addr/1}

                   UpdateFlashIndic
5a52: a5 93                        lda     play_mode_flag    ;are we playing?
5a54: f0 03                        beq     :CityArrows       ;no, branch
5a56: 4c f0 65                     jmp     UpdatePlyrIndic   ;yes, go do the 1up/2up arrow

5a59: a5 91        :CityArrows     lda     func_index        ;check which function we're in
5a5b: c9 04                        cmp     #FN_PLAY_GAME     ;game play demo?
5a5d: d0 22                        bne     :Return           ;no, bail
                   ; 
5a5f: a0 00                        ldy     #$00              ;fg color = 0
5a61: a5 d7                        lda     draw_1up_flag     ;are we in the visible phase?
5a63: d0 02                        bne     :SetColor         ;no, branch
5a65: a0 40                        ldy     #%01000000        ;fg color = 2
5a67: 84 b6        :SetColor       sty     ]color_tmp        ;save color to ZP
                   ; 
5a69: a2 05                        ldx     #$05              ;6 cities
5a6b: 86 ab        :Loop           stx     ]counter          ;set ZP counter
5a6d: a5 b6                        lda     ]color_tmp        ;get color
5a6f: 85 0b                        sta     draw_fg_color     ;set glyph foreground
5a71: a0 24                        ldy     #36               ;vertical position
5a73: bd e2 60                     lda     city_x_tbl,x      ;get horizontal position for city
5a76: aa                           tax                       ;put in X-reg
5a77: a9 1c                        lda     #$1c              ;down arrow glyph
5a79: 20 00 66                     jsr     DrawLetterByIdx   ;draw it
5a7c: a6 ab                        ldx     ]counter
5a7e: ca                           dex                       ;done with all cities?
5a7f: 10 ea                        bpl     :Loop             ;not yet, loop
5a81: 60           :Return         rts

                   ; 
                   ; Func $14: draws the "MISSILE COMMAND" title.
                   ; 
                   ; The words are drawn while the palette is entirely blacked out, so that instead
                   ; of visibily rendering they just pop onto the screen.
                   ; 
                   ]initial_delay_ctr .var $b2    {addr/1}   ;(using ICBM move counter var)
                   ]expl_rep_ctr   .var    $b3    {addr/1}   ;(using ICBM move counter var)

5a82: a9 10        F_ShowTitle     lda     #16               ;set counter to repeat explosion pattern 16x
5a84: 85 b3                        sta     ]expl_rep_ctr
5a86: a9 40                        lda     #64               ;set initial delay to 64 frames (~1 sec)
5a88: 85 b2                        sta     ]initial_delay_ctr
5a8a: a9 00                        lda     #$00
5a8c: 85 93                        sta     play_mode_flag    ;not playing a game right now
5a8e: 85 ec                        sta     color_cyc_mask    ;no cycling
                   ; 
5a90: a2 07                        ldx     #$07              ;palette has 8 entries
5a92: a9 0e                        lda     #%00001110        ;init them to black
5a94: 95 e4        :Loop           sta     color_palette,x
5a96: ca                           dex
5a97: 10 fb                        bpl     :Loop
                   ; 
5a99: 20 86 69                     jsr     ClearScreen       ;erase screen to color 0
5a9c: a9 1c                        lda     #$1c
5a9e: 20 51 6a                     jsr     PrintMessage      ;"MISSILE"
5aa1: a9 1d                        lda     #$1d
5aa3: 20 51 6a                     jsr     PrintMessage      ;"COMMAND"
5aa6: a9 06                        lda     #%00000110        ;set the 6th entry to red
5aa8: 85 ea                        sta     color_palette+6   ;this makes the words pop onto the screen
                   ; 
5aaa: a9 00                        lda     #$00
5aac: a2 13                        ldx     #19               ;20 entries in explosion table
5aae: 9d 6e 01     :Loop           sta     expl_ypos,x       ;zero out the Y coord to mark them as unused
5ab1: ca                           dex
5ab2: 10 fa                        bpl     :Loop
5ab4: a9 16                        lda     #FN_TITLE_EXPLS   ;switch to title explosion func
5ab6: 85 91                        sta     func_index
5ab8: 60                           rts

                   ; 
                   ; Func $16: draws title screen explosion sequence.
                   ; 
                   ; Draws and erases 20 explosions, sized and placed randomly.
                   ; 
                   ; This uses the 16-bit ICBM movement counter ($b2-b3) as a pair of counters,
                   ; initialized by func $14 (above).
                   ; 
5ab9: a5 b2        F_TitleExpls    lda     ]initial_delay_ctr ;has initial delay period finished?
5abb: f0 05                        beq     :ZeroB2           ;yes, branch
5abd: c6 b2                        dec     ]initial_delay_ctr ;no, update counter and bail
5abf: b8                           clv
5ac0: 50 0e                        bvc     :Return           ;(always)

5ac2: c6 b3        :ZeroB2         dec     ]expl_rep_ctr     ;have we done enough exploding?
5ac4: d0 07                        bne     :DoExpl           ;not yet, branch
                   ; 
5ac6: a9 00                        lda     #FN_INIT_GAME     ;move on to game init func
5ac8: 85 91                        sta     func_index
5aca: b8                           clv
5acb: 50 03                        bvc     :Return           ;(always)

5acd: 20 b3 62     :DoExpl         jsr     DrawTitleExpl     ;erase & redraw 20 explosions
5ad0: 60           :Return         rts

                   ; 
                   ; Func $00: initialize game data.
                   ; 
                   • Clear variables

5ad1: 20 2f 67     F_InitGame      jsr     InitLangAndFlip   ;set language and orientation
5ad4: a9 40                        lda     #%01000000        ;color = 2
5ad6: 85 ce                        sta     score_color       ;set color to use for scores
5ad8: a5 93                        lda     play_mode_flag    ;is a game in progress?
5ada: f0 03                        beq     :NoGame           ;no, branch
5adc: 20 d6 5f                     jsr     SetZeroScores     ;zero out the scores
5adf: a9 00        :NoGame         lda     #$00              ;init some globals
5ae1: 85 df                        sta     next_player
5ae3: 85 c1                        sta     num_cities+1
5ae5: 85 c6                        sta     live_city_mask+1
5ae7: 85 c3                        sta     bonus_award_sc
5ae9: 85 c4                        sta     bonus_award_sc+1
5aeb: a9 01                        lda     #$01
5aed: 85 a7                        sta     wave_num          ;initialize wave number
                   ; 
5aef: a6 ae                        ldx     num_players       ;get number of players
5af1: a5 f4                        lda     r8_irq_inv        ;get R8 switches
5af3: 29 03                        and     #%00000011        ;get initial city count setting
5af5: a8                           tay                       ;use as index
5af6: b9 08 5b     :Loop           lda     init_city_count,y ;get initial cities based on setting
5af9: 95 c0                        sta     num_cities,x      ;set for current player
5afb: b9 0c 5b                     lda     init_city_mask,y  ;get "live city" bit mask
5afe: 95 c5                        sta     live_city_mask,x  ;set for current player
5b00: ca                           dex                       ;done with both players?
5b01: 10 f3                        bpl     :Loop             ;no, loop
                   ; 
5b03: a9 02                        lda     #FN_PREP_WAVE
5b05: 85 91                        sta     func_index        ;switch to wave-prep function
5b07: 60                           rts

                   ; 
                   ; Initial number of cities, determined by R8 switches (Off=1, On=0):
                   ;   00 = 6
                   ;   01 = 4
                   ;   10 = 5
                   ;   11 = 7
                   ; 
5b08: 06 04 05 07  init_city_count .bulk   $06,$04,$05,$07
                   ; 
                   ; Live-city mask, indicating which cities are whole.  Cities are not arranged
                   ; from left to right; see the horizontal position table at $60e2.
5b0c: fc           init_city_mask  .dd1    %11111100         ;6 cities
5b0d: e8                           .dd1    %11101000         ;4 cities
5b0e: f8                           .dd1    %11111000         ;5 cities
5b0f: fc                           .dd1    %11111100         ;7 cities (6 + 1 bonus)

                   ; 
                   ; Func $02: initializes values at the start of a wave.
                   ; 
5b10: a5 df        F_PrepWave      lda     next_player       ;get next-player number (0/1)
5b12: 85 b9                        sta     cur_plyr_num      ;update current player number
5b14: 20 0c 7b                     jsr     InitPokey         ;reset sound
5b17: a9 10                        lda     #SFX_START_WAVE   ;alert
5b19: 20 ec 79                     jsr     StartSfx
5b1c: a5 93                        lda     play_mode_flag    ;is a game in progress?
5b1e: d0 09                        bne     :InitInc          ;yes, branch
5b20: a0 01                        ldy     #$01              ;no, always be on wave #1
5b22: 84 a7                        sty     wave_num
5b24: a9 12                        lda     #18               ;but with 18 incoming missiles (instead of 12)
5b26: b8                           clv
5b27: 50 0b                        bvc     :SetInc           ;(always)

                   ; Set the number of ICBMs and smartbombs.  In rev2, wave_num could be zero, and
                   ; those table entries happen to be zero, making wave 256 a freebie.
5b29: a4 a7        :InitInc        ldy     wave_num          ;get current wave number
5b2b: c0 13                        cpy     #19               ;is it >= 19?
5b2d: 90 02                        bcc     :Lt19             ;no, branch
5b2f: a0 13                        ldy     #19               ;cap at 19
5b31: b9 8f 60     :Lt19           lda     icbm_count_tbl-1,y ;get incoming ICBM count for this wave
5b34: 85 8d        :SetInc         sta     wave_icbm_count   ;set counter
5b36: be c0 60                     ldx     sbomb_count_tbl-1,y ;get incoming smartbomb count for this wave
5b39: 86 db                        stx     wave_sbomb_count  ;set counter
                   ; 
5b3b: a9 30                        lda     #%00110000        ;configure color palette cycling to rotate
5b3d: 85 ec                        sta     color_cyc_mask    ; the colors for indices 4 and 5
5b3f: 20 1a 67                     jsr     PrepWaveScreen    ;set colors, redraw city / silo area
5b42: 20 9e 5b                     jsr     SetMultAndSpeed   ;set score mult and incoming entity speeds
                   ; Init per-wave globals.
5b45: a9 ff                        lda     #$ff
5b47: 85 c2                        sta     mirv_slot
5b49: a9 00                        lda     #$00
5b4b: 85 bf                        sta     full_speed_flag
5b4d: 85 96                        sta     next_expl_slot
5b4f: 85 8e                        sta     num_explosions
5b51: 85 90                        sta     num_expl_icbms
5b53: 85 8f                        sta     num_expl_abms
5b55: 85 da                        sta     num_live_bombs
5b57: 85 c9                        sta     flier_fire_ctr
5b59: 85 d9                        sta     smartbomb_mask
5b5b: 85 8c                        sta     num_live_icbms
5b5d: 85 d0                        sta     sbomb_dir
5b5f: 85 fa                        sta     fire_buttons
5b61: 85 b4                        sta     exp_seq_index
5b63: 85 cc                        sta     silo_targ_flags
5b65: 85 cb                        sta     city_targ_flags
5b67: 85 b3                        sta     icbm_move_ctr+1
5b69: 85 cf                        sta     cities_destroyed
                   ; Init tables used for ABMs and ICBMs, and the flier.
5b6b: a2 aa                        ldx     #$aa
5b6d: 9d 17 01     :ZLoop          sta     abm_cur_xfrac-1,x ;zero out $118-1c1
5b70: ca                           dex
5b71: d0 fa                        bne     :ZLoop
                   ; 
5b73: a9 e0                        lda     #%11100000
5b75: 85 cd                        sta     live_silo_mask    ;mark all silos as alive
5b77: a5 93                        lda     play_mode_flag    ;are we playing?
5b79: f0 03                        beq     :NoPlay           ;no, branch
5b7b: 20 ce 65                     jsr     DrawPlyrIndic     ;draw 1up/2up indicator
5b7e: 20 16 5f     :NoPlay         jsr     PreWaveMsgs       ;draw pre-wave messages
                   ; 
5b81: a0 02                        ldy     #2                ;three ABM silos
5b83: a9 0a        :AbmLoop        lda     #10               ;10 ABMs per silo
5b85: 99 a0 00                     sta     abms_left,y       ;full set of missiles
5b88: 20 6a 68                     jsr     DrawAllAmmo       ;draw all ammo for this silo
5b8b: 88                           dey                       ;finished all 3 silos?
5b8c: 10 f5                        bpl     :AbmLoop          ;no, loop
                   ; 
5b8e: 20 5c 65                     jsr     InitCrosshairs    ;put crosshairs in low-center position
5b91: a9 04                        lda     #FN_PLAY_GAME     ;queue up the play-game func
5b93: 85 92                        sta     next_func_index
5b95: a9 22                        lda     #FN_PAUSE         ;but pause briefly first
5b97: 85 91                        sta     func_index
5b99: a9 1e                        lda     #30               ;delay for 30*4 frames (~2 sec)
5b9b: 85 af                        sta     frame4_delay_ctr
5b9d: 60                           rts

                   ; 
                   ; Sets score multiplier based on the current wave.  Sets the base score value,
                   ; to 25 * multiplier.  All scores awarded are multiples of this, except for
                   ; unfired ABMs.
                   ; 
                   ; Sets the wave-specific characteristics of ICBMs and fliers, such as speed.
                   ; 
                   ; On exit:
                   ;   $ba-bb: base score configured
                   ; 
5b9e: a9 00        SetMultAndSpeed lda     #$00              ;init base score to zero
5ba0: 85 ba                        sta     base_score_bcd
5ba2: 85 bb                        sta     base_score_bcd+1
5ba4: a5 a7                        lda     wave_num          ;get current wave number (starts at 1)
5ba6: 18                           clc
5ba7: 69 01                        adc     #$01              ;add 1 so wave 1/2 is 1x, wave 3/4 is 2x
5ba9: 4a                           lsr     A                 ;cut it in half so we incr every other wave
5baa: c9 06                        cmp     #$06              ;less than 6?
5bac: 90 02                        bcc     :Lt6              ;yes, branch
5bae: a9 06                        lda     #$06              ;cap at 6x
5bb0: 85 dd        :Lt6            sta     score_mult        ;save multiplier
5bb2: aa                           tax                       ;init count to multiplier
                   ; Compute 25 * multiplier.
                   ; In rev2, if wave_num is $ff (wave 255), adding one makes it $00, which causes
                   ; this to execute 256 times.  This yields a base score of 25*256=6400.  The same
                   ; problem also happens on wave 256: ($00+1)/2=0.  Rev3 avoids the problem by
                   ; resetting the wave number to 20 when it hits 40.
5bb3: f8           :MultLoop       sed                       ;enable decimal mode
5bb4: a5 ba                        lda     base_score_bcd    ;get current value
5bb6: 18                           clc
5bb7: 69 25                        adc     #$25              ;add 25
5bb9: 85 ba                        sta     base_score_bcd
5bbb: a5 bb                        lda     base_score_bcd+1
5bbd: 69 00                        adc     #$00              ;carry into high byte
5bbf: 85 bb                        sta     base_score_bcd+1
5bc1: d8                           cld                       ;disable decimal mode
5bc2: ca                           dex                       ;done yet?
5bc3: d0 ee                        bne     :MultLoop         ;no, branch
                   ; 
5bc5: a4 a7                        ldy     wave_num          ;get wave number
5bc7: c0 0f                        cpy     #15               ;less than 15?
5bc9: 90 02                        bcc     :Lt15             ;yes, branch
5bcb: a0 0f                        ldy     #15               ;cap at 15
5bcd: b9 a2 60     :Lt15           lda     icbm_spd_frac_tbl-1,y ;configure ICBM speed, as an 8.8 frame delay
5bd0: 85 b7                        sta     icbm_move_delay
5bd2: b9 b1 60                     lda     icbm_spd_int_tbl-1,y
5bd5: 85 b8                        sta     icbm_move_delay+1
                   ; 
5bd7: c0 08                        cpy     #8                ;wave number less than 8?
5bd9: 90 02                        bcc     :Lt8              ;yes, branch
5bdb: a0 08                        ldy     #8                ;cap at 8
5bdd: b9 d2 60     :Lt8            lda     flier_msl_delay_tbl-2,y ;get flier firing delay
5be0: 85 e1                        sta     flier_fire_delay  ;set value
5be2: b9 d9 60                     lda     flier_charge_int_tbl-2,y ;get flier recharge interval
5be5: 85 e2                        sta     flier_charge_int  ;set value
5be7: 85 e3                        sta     flier_charge_ctr  ;also set counter, so flier can launch immediately
5be9: 60                           rts

                   ; 
                   ; Func $08: end wave.
                   ; 
                   ; Transitions to the ABM bonus tally, city bonus tally, or game-over check.
                   ; 
5bea: 20 37 5e     F_EndOfWave     jsr     EraseIncoming     ;remove ICBM trails and smartbombs
5bed: 20 72 65                     jsr     HideCrosshairs    ;hide the crosshairs
5bf0: 20 96 5f                     jsr     ZeroBcdAcc        ;prep BCD accumulator for bonus tally
5bf3: a9 00                        lda     #$00
5bf5: 85 97                        sta     slot_index        ;init counter used by ABM bonus screen
5bf7: a2 10                        ldx     #FN_WAVE_DONE_UPD ;assume next step will be post-wave update
5bf9: a5 93                        lda     play_mode_flag    ;game in progress?
5bfb: f0 20                        beq     :StxReturn        ;no, bail
                   ; 
5bfd: a5 a0                        lda     abms_left         ;any ABMs left to fire?
5bff: 05 a1                        ora     abms_left+1
5c01: 05 a2                        ora     abms_left+2
5c03: f0 0a                        beq     :NoAbms           ;no, branch
5c05: a9 07                        lda     #$07              ;"BONUS POINTS"
5c07: 20 55 6a                     jsr     PrintMessagePr
5c0a: a2 0a                        ldx     #FN_ABM_BONUS     ;next step: award points for leftover ABMs
5c0c: b8                           clv
5c0d: 50 0e                        bvc     :StxReturn

5c0f: a4 b9        :NoAbms         ldy     cur_plyr_num      ;get current player number
5c11: b9 c5 00                     lda     live_city_mask,y  ;any surviving cities?
5c14: f0 07                        beq     :StxReturn        ;no, bail
5c16: a9 07                        lda     #$07
5c18: 20 55 6a                     jsr     PrintMessagePr    ;"BONUS POINTS"
5c1b: a2 0c                        ldx     #FN_CITY_BONUS_CHK ;next step: award points for surviving cities
5c1d: 86 91        :StxReturn      stx     func_index
5c1f: 60                           rts

                   ; 
                   ; Func $0a: award bonus points for unfired ABMs.
                   ; 
                   ; This is called repeatedly.  If not enough time has passed, it returns
                   ; immediately.  Otherwise, it finds the next unfired ABM and awards bonus points
                   ; for it.
                   ; 
                   ; The global normally used to pass a slot index ($97) is used here to keep track
                   ; of the screen position for the ABM icon display.
                   ; 
5c20: c6 b4        F_AbmBonusPts   dec     exp_seq_index     ;decrement counter used to pace frames
5c22: 10 5d                        bpl     :Return           ;not ready to draw yet; bail
                   ; Award bonus points for un-fired ABMs.
5c24: a0 02                        ldy     #$02              ;walk through 3 silos
5c26: b9 a0 00     :SiloLoop       lda     abms_left,y       ;get number of ABMs remaining in silo
5c29: f0 3b                        beq     :SiloEmpty
5c2b: 38                           sec
5c2c: e9 01                        sbc     #$01              ;subtract 1
5c2e: 99 a0 00                     sta     abms_left,y       ;write it back
5c31: 20 d1 67                     jsr     EraseAmmo         ;erase that icon from the launcher
5c34: 20 bb 5f                     jsr     AddBcd5x          ;add (5 * multiplier) points
5c37: a2 40                        ldx     #64               ;X coord
5c39: a0 80                        ldy     #128              ;Y coord
5c3b: a9 40                        lda     #%01000000        ;color #2
5c3d: 20 f6 6a                     jsr     PrintBcdNumber    ;print score
5c40: a9 82                        lda     #130              ;vertical position
5c42: 49 ff                        eor     #$ff              ;invert to form pointer
5c44: 85 07                        sta     gfx_ptr+1         ;set as high byte
                   ; 
5c46: a5 97                        lda     slot_index        ;get icon counter
5c48: 0a                           asl     A                 ;multiply by 4
5c49: 0a                           asl     A
5c4a: 18                           clc
5c4b: 69 7a                        adc     #122              ;start near center of screen
5c4d: 24 fc                        bit     cocktail_flip     ;are we flipped?
5c4f: 50 02                        bvc     :NoFlip           ;no, branch
5c51: 49 ff                        eor     #$ff              ;invert X coord
5c53: 85 06        :NoFlip         sta     gfx_ptr           ;set as low byte of pointer
                   ; 
5c55: a9 e0                        lda     #%11100000        ;color = 7
5c57: 20 ed 67                     jsr     DrawAmmoIcon      ;draw ammo icon at pointer
5c5a: e6 97                        inc     slot_index        ;increment icon counter
5c5c: a9 05                        lda     #$05
5c5e: 85 b4                        sta     exp_seq_index     ;reset delay counter
5c60: a9 08                        lda     #SFX_BONUS_PTS
5c62: 20 ec 79                     jsr     StartSfx          ;play "thup" sound
5c65: 60                           rts

5c66: 88           :SiloEmpty      dey                       ;move to next silo; done yet?
5c67: 10 bd                        bpl     :SiloLoop         ;no, loop
                   ; 
5c69: a9 0c                        lda     #FN_CITY_BONUS_CHK ;check for city bonus next
5c6b: 85 92                        sta     next_func_index
5c6d: a2 0f                        ldx     #15               ;delay for 15 frames (~0.25 sec)
5c6f: a4 b9                        ldy     cur_plyr_num      ;get current player number
5c71: b9 c5 00                     lda     live_city_mask,y  ;get bit map of surviving cities
5c74: f0 02                        beq     :NoCity           ;wiped clean, branch
5c76: a2 00                        ldx     #$00              ;cancel delay
5c78: 86 af        :NoCity         stx     frame4_delay_ctr  ;store delay value
5c7a: 20 13 60                     jsr     AddBcdToScore     ;add bonus points to player score
5c7d: a9 22                        lda     #FN_PAUSE         ;pause briefly before moving on
5c7f: 85 91                        sta     func_index
5c81: 60           :Return         rts

                   ; 
                   ; Func $0c: checks to see if we need to award a bonus for cities.
                   ; 
5c82: a9 10        F_CityBonusChk  lda     #$10              ;default to end-of-wave housekeeping func
5c84: 85 91                        sta     func_index
5c86: a4 b9                        ldy     cur_plyr_num      ;get current player number
5c88: b9 c5 00                     lda     live_city_mask,y  ;get live city bitmap
5c8b: 25 93                        and     play_mode_flag    ;zero it out if no game in progress
5c8d: f0 0b                        beq     :NoBonus          ;no cities, don't show bonus
                   ; Count the number of surviving cities.
5c8f: a2 00                        ldx     #$00              ;init count
5c91: 0a           :CountLoop      asl     A                 ;shift high bit into carry
5c92: 90 01                        bcc     :NotLive          ;not set, branch
5c94: e8                           inx                       ;set, add one to counter
5c95: c9 00        :NotLive        cmp     #$00              ;have we examined all bits?
5c97: d0 f8                        bne     :CountLoop        ;not yet, branch
5c99: 8a                           txa                       ;copy to A-reg to set status flags
5c9a: f0 0d        :NoBonus        beq     :Return           ;no live cities, bail
                   ; 
5c9c: 86 97                        stx     slot_index        ;save city count for func $0e
5c9e: a9 00                        lda     #$00
5ca0: 85 8d                        sta     wave_icbm_count   ;init bonus city icon position index
5ca2: a9 0e                        lda     #FN_CITY_BONUS_PTS
5ca4: 85 91                        sta     func_index        ;switch to the bonus city func
5ca6: 20 96 5f                     jsr     ZeroBcdAcc        ;prep BCD accumulator for bonus tally
5ca9: 60           :Return         rts

                   ; 
                   ; Func $0e: awards bonus points for surviving cities.
                   ; 
                   ; Each call shows one more city.  We delay for a few frames between each.
                   ; 
                   ; The code here (mis-)uses a couple of gameplay globals.  The explosion sequence
                   ; index ($b4) is used as a delay timer, the ICBM counter ($8d) holds the index
                   ; of the next city to draw, and the slot index ($97) has the number of live
                   ; cities.  The latter two were initialized in func $0c, above.  The explosion
                   ; sequence index is a small value (0-4) so not necessary to init.
                   ; 
                   ]city_bits      .var    $98    {addr/1}

5caa: c6 b4        F_CityBonusPts  dec     exp_seq_index     ;use this as delay timer
5cac: 10 5d                        bpl     :Return           ;not done yet, return
                   ; 
5cae: a4 b9                        ldy     cur_plyr_num      ;get current player number
5cb0: b9 c5 00                     lda     live_city_mask,y  ;get surviving cities
5cb3: 85 98                        sta     ]city_bits        ;save in ZP
5cb5: a6 97                        ldx     slot_index        ;get number of cities remaining
5cb7: a0 06                        ldy     #6                ;test six bits, starting with high bit
5cb9: 88           :FindLoop       dey
5cba: a5 98                        lda     ]city_bits        ;get bits
5cbc: 39 f7 60                     and     single_bits,y     ;mask off all but bit (7-N)
5cbf: f0 01                        beq     :SkipDec          ;not set, branch
5cc1: ca                           dex                       ;set, decrement counter
5cc2: e0 00        :SkipDec        cpx     #$00              ;has the counter reached zero?
5cc4: d0 f3                        bne     :FindLoop         ;not yet, keep shifting
                   ; Y-reg now holds the index of the live city.
5cc6: 20 53 67                     jsr     EraseCityNoFlip   ;remove city from bottom of screen
5cc9: a2 03                        ldx     #$03              ;iterate 4x, because cities are worth
5ccb: 20 a2 5f                     jsr     AddBaseScoreX     ; (25 * mult) * 4 points
5cce: a2 40                        ldx     #64               ;put on left quarter of screen
5cd0: a0 60                        ldy     #96               ;set vertical position
5cd2: a9 40                        lda     #%01000000        ;color = 2
5cd4: 20 f6 6a                     jsr     PrintBcdNumber    ;print current tally
                   ; Draw one city icon in bonus tabulation area.
5cd7: a5 8d                        lda     wave_icbm_count   ;get counter
5cd9: e6 8d                        inc     wave_icbm_count   ;increment for next iteration
5cdb: 0a                           asl     A                 ;multiply by 16
5cdc: 0a                           asl     A                 ; because city icons are 16x8
5cdd: 0a                           asl     A
5cde: 0a                           asl     A
5cdf: 18                           clc
5ce0: 65 8d                        adc     wave_icbm_count   ;add one to make it x17
5ce2: 18                           clc                       ; to leave a little space between city icons
5ce3: 69 80                        adc     #128              ;relative to center of screen
5ce5: aa                           tax                       ;use as horizontal position
5ce6: a0 60                        ldy     #96               ;set vertical position
5ce8: a9 e4                        lda     #%11100100        ;color 7 and 2
5cea: 20 95 67                     jsr     DrawCity          ;draw the city
                   ; 
5ced: a9 08                        lda     #SFX_BONUS_PTS
5cef: 20 ec 79                     jsr     StartSfx          ;make the bonus-point noise
5cf2: a9 0a                        lda     #10               ;wait for 10 frames between cities
5cf4: 85 b4                        sta     exp_seq_index
5cf6: c6 97                        dec     slot_index        ;more cities to do?
5cf8: d0 11                        bne     :Return           ;yes, bail
                   ; All city bonuses have been awarded; move on.
5cfa: a9 10                        lda     #FN_WAVE_DONE_UPD ;do the end-of-wave housekeeping func...
5cfc: 85 92                        sta     next_func_index
5cfe: a9 22                        lda     #FN_PAUSE         ;...after we delay for a bit
5d00: 85 91                        sta     func_index
5d02: a9 0f                        lda     #15               ;pause for 15*4 frames (about 1 sec)
5d04: 85 af                        sta     frame4_delay_ctr
5d06: a4 b9                        ldy     cur_plyr_num      ;get current player number
5d08: 20 13 60                     jsr     AddBcdToScore     ;add the bonus points to the score
5d0b: 60           :Return         rts

                   ; 
                   ; Func $10: handles housekeeping at the end of a wave.
                   ; 
                   ; If this is a two-player game, switch to the other player, unless one of them
                   ; is dead.  If player 1 is playing, increment the wave number.
                   ; 
                   ; If both players are dead, tee up a high score check.
                   ; 
                   F_WaveDoneUpdate
5d0c: a9 02                        lda     #FN_PREP_WAVE     ;do wave prep func next, after optional delay
5d0e: 85 92                        sta     next_func_index
5d10: a9 00                        lda     #$00              ;default to no delay
5d12: 85 af                        sta     frame4_delay_ctr
5d14: a9 22                        lda     #FN_PAUSE         ;do the pre-game delay func next
5d16: 85 91                        sta     func_index
5d18: 20 ed 5d                     jsr     RebuildCities     ;merge in bonus cities if needed
5d1b: a5 c0                        lda     num_cities        ;get player 1 city count
5d1d: 05 c1                        ora     num_cities+1      ;combine with player 2 city count
5d1f: 25 93                        and     play_mode_flag    ;zero it out if game not in progress
5d21: d0 07                        bne     :StillAlive
5d23: a9 18                        lda     #FN_HS_CHECK      ;whoops, all dead; switch to high score check func
5d25: 85 91                        sta     func_index
5d27: b8                           clv
5d28: 50 42                        bvc     :Return           ;(always)

                   ; At least one player is still alive.  See if the current player died.
5d2a: a4 ae        :StillAlive     ldy     num_players       ;get number of players (0/1)
5d2c: f0 1d                        beq     :NoNewDeath       ;single player, must be alive; branch
5d2e: a4 b9                        ldy     cur_plyr_num      ;get current player (0/1)
5d30: b9 c0 00                     lda     num_cities,y      ;get number of cities
5d33: d0 16                        bne     :NoNewDeath       ;still have a city, branch
                   ; Say "game over" to current player, then move on to other player.
5d35: a9 07                        lda     #$07              ;erase lines with these messages
5d37: 20 49 6a                     jsr     EraseMessageLine  ;"BONUS POINTS"
5d3a: a9 09                        lda     #$09
5d3c: 20 49 6a                     jsr     EraseMessageLine  ;"GREAT SCORE"
5d3f: a9 0e                        lda     #$0e              ;break the bad news
5d41: 20 55 6a                     jsr     PrintMessagePr    ;"GAME OVER"
5d44: 20 48 5f                     jsr     PrintPlayerN      ;"PLAYER N"
5d47: a9 0f                        lda     #15
5d49: 85 af                        sta     frame4_delay_ctr  ;configure 15*4 frame (~1 sec) pause
                   ; 
5d4b: a5 b9        :NoNewDeath     lda     cur_plyr_num      ;get current player number
5d4d: 85 df                        sta     next_player       ;copy here
5d4f: a4 ae        :Loop           ldy     num_players       ;get number of players - 1 (0/1)
5d51: f0 10                        beq     :Single2
5d53: c4 df                        cpy     next_player       ;was player 2 playing?
5d55: d0 07                        bne     :Player1up        ;no, branch
5d57: c6 df                        dec     next_player       ;yes, switch to player 1
5d59: e6 a7                        inc     wave_num          ;advance the wave
5d5b: b8                           clv
5d5c: 50 02                        bvc     :Br_CheckCity     ;branch

5d5e: e6 df        :Player1up      inc     next_player       ;switch to player 2 (WITHOUT advancing wave)
5d60: b8           :Br_CheckCity   clv
5d61: 50 02                        bvc     :CheckAlive       ;(always)

5d63: e6 a7        :Single2        inc     wave_num          ;advance to next wave
                   ; 
5d65: a4 df        :CheckAlive     ldy     next_player       ;for 2-player game, make sure the player we
5d67: b9 c0 00                     lda     num_cities,y      ; switched to is alive
5d6a: f0 e3                        beq     :Loop             ;if not, loop to switch back
5d6c: 60           :Return         rts

                   ; 
                   ; Func $06: game over.
                   ; 
                   ; Clean up some things and prep for the end-game animation.
                   ; 
5d6d: a2 1c        F_GameOver      ldx     #FN_SHOW_HS       ;default to showing high scores next
5d6f: a5 93                        lda     play_mode_flag    ;is a game in progress?
5d71: f0 1e                        beq     :NoGame           ;no, branch
5d73: a9 06                        lda     #%00000110        ;red
5d75: 85 e4                        sta     color_palette     ;set all palette entries except color 4
5d77: 85 e5                        sta     color_palette+1
5d79: 85 e6                        sta     color_palette+2
5d7b: 85 e7                        sta     color_palette+3
5d7d: 85 e9                        sta     color_palette+5
5d7f: 85 ea                        sta     color_palette+6
5d81: 85 eb                        sta     color_palette+7
5d83: a9 80                        lda     #%10000000        ;color = 4 (cycles)
5d85: 85 ce                        sta     score_color
5d87: 20 e1 5f                     jsr     PrintScores       ;print player scores in flashing text
5d8a: a9 20                        lda     #SFX_GAME_OVER
5d8c: 20 ec 79                     jsr     StartSfx          ;play long explosion sound
5d8f: a2 12                        ldx     #FN_THE_END       ;show "the end" screen
5d91: 86 91        :NoGame         stx     func_index
                   ; 
5d93: a0 00                        ldy     #$00
5d95: 8c c3 01                     sty     expl_radius_idx_arr+1 ;init "before" expl size for "the end"
5d98: 84 ae                        sty     num_players       ;reset number of players
5d9a: 84 93                        sty     play_mode_flag    ;clear game-in-progress flag
5d9c: a0 01                        ldy     #$01
5d9e: 8c c2 01                     sty     expl_radius_idx_arr ;init "next" expl size for "the end"
5da1: 60                           rts

                   ; 
                   ; Func $12: show end-game animation.
                   ; 
                   • Clear variables
                   ]after_radius   .var    $9a    {addr/1}
                   ]center_x       .var    $9b    {addr/1}
                   ]center_y       .var    $9c    {addr/1}
                   ]before_radius  .var    $b5    {addr/1}

5da2: ad c2 01     F_ShowEndTimes  lda     expl_radius_idx_arr ;get current explosion radius from slot #0
5da5: 85 b5                        sta     ]before_radius    ;save for use as arg
5da7: f0 3c                        beq     :Next1c           ;if we're at zero, animation complete; branch
5da9: c9 6d                        cmp     #109              ;have we reached max size?
5dab: 90 05                        bcc     :CheckExpand      ;not yet, branch
5dad: a9 6c                        lda     #108              ;start contracting
5daf: b8                           clv
5db0: 50 1b                        bvc     :Draw             ;(always)

5db2: cd c3 01     :CheckExpand    cmp     expl_radius_idx_arr+1 ;are we expanding or contracting?
5db5: b0 06                        bcs     :Expand           ;no radius is bigger, expand
5db7: 38                           sec
5db8: e9 01                        sbc     #$01              ;shrink by one
5dba: b8                           clv
5dbb: 50 10                        bvc     :Draw             ;(always)

5dbd: 18           :Expand         clc
5dbe: 69 01                        adc     #$01              ;expand by one
5dc0: 48                           pha                       ;save new radius
5dc1: c9 62                        cmp     #98               ;are we at the point where we draw text?
5dc3: d0 05                        bne     :NoText           ;not yet, branch
                   ; 
5dc5: a9 08                        lda     #$08
5dc7: 20 55 6a                     jsr     PrintMessagePr    ;show "THE END"
                   ; 
5dca: c6 b5        :NoText         dec     ]before_radius    ;make "before" one less than current
5dcc: 68                           pla                       ;restore new radius
5dcd: 8d c2 01     :Draw           sta     expl_radius_idx_arr ;save new radius
5dd0: 85 9a                        sta     ]after_radius     ;use as the "after" value
5dd2: a4 b5                        ldy     ]before_radius    ;copy "before" radius to explosion slot #1
5dd4: 8c c3 01                     sty     expl_radius_idx_arr+1
5dd7: a9 80                        lda     #128              ;draw at center of screen
5dd9: 85 9b                        sta     ]center_x
5ddb: a9 73                        lda     #115
5ddd: 85 9c                        sta     ]center_y
5ddf: 20 71 5e                     jsr     DrawExplosion     ;draw next ring of the octagon
5de2: b8                           clv
5de3: 50 07                        bvc     :Return           ;(always)

5de5: a9 1c        :Next1c         lda     #FN_SHOW_HS       ;show high scores next
5de7: 85 91                        sta     func_index
5de9: 20 0c 7b                     jsr     InitPokey         ;silence audio
5dec: 60           :Return         rts

                   ; 
                   ; Awards bonus cities, and fills craters if bonus cities are available.
                   ; 
                   • Clear variables
                   ]city_map       .var    $98    {addr/1}
                   ]counter        .var    $dc    {addr/1}

5ded: a5 93        RebuildCities   lda     play_mode_flag    ;is a game in progress?
5def: f0 45                        beq     :Return           ;no, skip this
5df1: 20 40 60                     jsr     CheckBonusCity    ;award bonus cities
5df4: a4 b9                        ldy     cur_plyr_num      ;get player number
5df6: b6 c0                        ldx     num_cities,y      ;get number of remaining cities
5df8: e0 06                        cpx     #$06              ;is it >= 6?
5dfa: 90 02                        bcc     :LE6              ;no, branch
                   ; 
5dfc: a2 06                        ldx     #$06              ;start the crater count at 6
5dfe: b9 c5 00     :LE6            lda     live_city_mask,y  ;get bit map of live cities
5e01: 85 98                        sta     ]city_map         ;store in ZP
5e03: 0a           :CountLoop      asl     A                 ;shift high bit into carry
5e04: 90 01                        bcc     :NotSet           ;not set
5e06: ca                           dex                       ;set, reduce number of craters by one
5e07: a8           :NotSet         tay                       ;set status flags for A-reg
5e08: d0 f9                        bne     :CountLoop        ;more bits to go, loop
5e0a: 8a                           txa                       ;set status flags for X-reg
5e0b: f0 29                        beq     :Return           ;no craters, bail
5e0d: 86 dc                        stx     ]counter          ;save crater count in ZP
                   ; 
5e0f: a4 b9        :Loop           ldy     cur_plyr_num      ;get current player number
5e11: b9 c5 00                     lda     live_city_mask,y  ;get mask with bits set for live cities
5e14: 49 fc                        eor     #%11111100        ;invert, so bits are set for craters
5e16: a0 00                        ldy     #$00
5e18: 20 1d 58                     jsr     PickRndBit        ;pick a crater at random
5e1b: a6 b9                        ldx     cur_plyr_num      ;get player number again
5e1d: b5 c5                        lda     live_city_mask,x  ;get live city bit mask
5e1f: 19 f7 60                     ora     single_bits,y     ;mark the selected crater as alive again
5e22: 95 c5                        sta     live_city_mask,x  ;save it
5e24: c6 dc                        dec     ]counter          ;done creating new cities?
5e26: d0 e7                        bne     :Loop             ;no, loop
                   ; 
5e28: a9 40                        lda     #SFX_BONUS_CITY
5e2a: 20 ec 79                     jsr     StartSfx          ;play series of short random tones
5e2d: a9 05                        lda     #$05
5e2f: 20 55 6a                     jsr     PrintMessagePr    ;"BONUS CITY"
5e32: a9 2d                        lda     #45               ;pause for 45*4 frames (~3 sec)
5e34: 85 af                        sta     frame4_delay_ctr
5e36: 60           :Return         rts

                   ; 
                   ; Erases all incoming missiles and smartbombs from the screen.
                   ; 
5e37: a5 8c        EraseIncoming   lda     num_live_icbms    ;get number of active ICBMs
5e39: 05 da                        ora     num_live_bombs    ;combine with number of active bombs
5e3b: f0 2c                        beq     :NoLive
5e3d: a2 07                        ldx     #$07              ;walk through 8 entries
5e3f: 86 dc                        stx     ]counter
5e41: a6 dc        :Loop           ldx     ]counter
5e43: bd 65 01                     lda     icbm_cur_y,x      ;is this entry active?
5e46: f0 1d                        beq     :Next             ;no, branch
5e48: 8a                           txa
5e49: 18                           clc
5e4a: 69 08                        adc     #$08              ;add 8 to convert ICBM index to missile index
5e4c: 85 97                        sta     slot_index        ;set slot index
5e4e: a5 d9                        lda     smartbomb_mask    ;get the smartbomb mask
5e50: 3d f7 60                     and     single_bits,x     ;is this entry a bomb?
5e53: d0 06                        bne     :Bomb             ;yes, branch
5e55: 20 86 66                     jsr     EraseTrail        ;no, erase missile trail
5e58: b8                           clv
5e59: 50 0a                        bvc     :Next             ;(always)

5e5b: 20 ac 77     :Bomb           jsr     EraseSmartBomb    ;erase smartbomb image
5e5e: a9 00                        lda     #$00
5e60: 85 da                        sta     num_live_bombs    ;set number of live bombs to zero
5e62: 20 e9 7a                     jsr     DisableBombSound  ;be quiet
                   ; 
5e65: c6 dc        :Next           dec     ]counter          ;done with all incoming ordnance?
5e67: 10 d8                        bpl     :Loop             ;no, loop
                   ; 
5e69: 20 0c 7b     :NoLive         jsr     InitPokey
5e6c: a9 0f                        lda     #$0f              ;clear the bottom line of the screen
5e6e: 4c 49 6a                     jmp     EraseMessageLine  ;"OUT" (left); erases full line

                   ; 
                   ; Draws an explosion.  Explosions look vaguely circular but are actually
                   ; octagonal.  They're drawn as a series of outline shapes, which over the course
                   ; of multiple frames form a solid shape.
                   ; 
                   ; This routine takes start/end radii and a center point.  If the start radius is
                   ; less than the end, the octagon is expanding, and will be drawn in the
                   ; foreground color (4/5).  If the start radius is greater, then octagon is
                   ; collapsing, and will be drawn in the background color.
                   ; 
                   ; On entry:
                   ;   $9a: end radius (exclusive)
                   ;   $9b: X coord
                   ;   $9c: Y coord
                   ;   $b5: start radius
                   ; 
                   ; On exit:
                   ;   (X-reg preserved)
                   ; 
                   • Clear variables
                   ]end_radius     .var    $9a    {addr/1}
                   ]xc             .var    $9b    {addr/1}
                   ]yc             .var    $9c    {addr/1}
                   ]saved_x        .var    $ac    {addr/1}
                   ]start_radius   .var    $b5    {addr/1}
                   ]color          .var    $b6    {addr/1}

5e71: 86 ac        DrawExplosion   stx     ]saved_x          ;preserve X-reg
5e73: a5 9a                        lda     ]end_radius       ;get the end radius
5e75: c5 b5                        cmp     ]start_radius     ;compare to start radius
5e77: f0 23                        beq     :Done             ;if start == end, nothing to do
5e79: b0 12                        bcs     :Expand           ;end > start, we're in the expansion phase
                   ; Shrinking.
5e7b: a9 00                        lda     #$00              ;color = 0
5e7d: 85 b6                        sta     ]color
5e7f: 20 9f 5e     :SLoop          jsr     DrawOctagon       ;draw octagon outline
5e82: a5 b5                        lda     ]start_radius
5e84: c6 b5                        dec     ]start_radius     ;decrement radius
5e86: c5 9a                        cmp     ]end_radius       ;reached the end?
5e88: d0 f5                        bne     :SLoop            ;not yet, loop
5e8a: b8                           clv
5e8b: 50 0f                        bvc     :Done             ;(always)

                   ; Expanding.
5e8d: a9 80        :Expand         lda     #%10000000        ;color = 4 (cycles)
5e8f: 85 b6                        sta     ]color
5e91: 20 9f 5e     :ELoop          jsr     DrawOctagon       ;draw octagon outline
5e94: a5 b5                        lda     ]start_radius
5e96: c5 9a                        cmp     ]end_radius       ;reached the end?
5e98: e6 b5                        inc     ]start_radius     ;increment radius
5e9a: 90 f5                        bcc     :ELoop            ;not at end yet, loop
                   ; 
5e9c: a6 ac        :Done           ldx     ]saved_x          ;restore X-reg
5e9e: 60                           rts

                   ; 
                   ; Draws octagon for explosion effect.
                   ; 
                   ; Essentially we draw a shallow line (slope=3/8) in the first octant, and
                   ; reflect it to the 7 others.  A perfect octagon would use slope=1/2, which you
                   ; can compare by NOPing $5ebc-5ec0.
                   ; 
                   ; The clearest way to see the shape is to watch the "THE END" animation.  (FWIW,
                   ; I think the perfect octagon looks better for small explosions, because it
                   ; reduces the spikes at the corners, but this shape feels better for the big
                   ; animation at the end.)
                   ; 
                   ; Example with radius 24:
                   ;   start_radius is 24
                   ;   first iteration 24/0: we double-draw points at (-24,0) (24,0),
                   ;     and (0,-24) (0,24)
                   ;   next iteration 24/1: we draw points at (-24,-1) (-24,1) (24,-1) (24,1)
                   ;     and (-1,-24) (-1,24) (1,-24) (1,24)
                   ;   then 24/2
                   ;   then 23/3, 23/4, 23/5
                   ;   then 22/6, ...
                   ;   eventually the deltas meet at 18/18
                   ; 
                   ; On entry:
                   ;   $9b/9c: center X/Y
                   ;   $b5: radius
                   ;   $b6: color (%PPx00000)
                   ; 
                   vis
                   ]deltaX         .var    $9d    {addr/1}
                   ]deltaY         .var    $9e    {addr/1}

5e9f: a6 b5        DrawOctagon     ldx     ]start_radius
5ea1: 86 9d                        stx     ]deltaX           ;start with xdelta = radius
5ea3: a2 00                        ldx     #$00
5ea5: 86 9e                        stx     ]deltaY           ;start with ydelta = 0
                   ; 
5ea7: 20 cd 5e     :Loop           jsr     DrawFourPix       ;draw 4 reflected points
5eaa: a6 9d                        ldx     ]deltaX           ;transpose X/Y deltas
5eac: a5 9e                        lda     ]deltaY
5eae: 86 9e                        stx     ]deltaY
5eb0: 85 9d                        sta     ]deltaX
5eb2: 20 cd 5e                     jsr     DrawFourPix       ;draw 4 reflected points
                   ; Update delta values, un-transposing X/Y:
                   ;   ydelta = xdelta + 1
                   ;   xdelta = start_radius - (ydelta * 3) / 8
5eb5: e6 9d                        inc     ]deltaX
5eb7: a5 9d                        lda     ]deltaX
5eb9: 85 9e                        sta     ]deltaY           ;copy xdelta to ydelta
5ebb: 4a                           lsr     A                 ;divide by 2
5ebc: 18                           clc
5ebd: 65 9e                        adc     ]deltaY           ;add to original to get 3/2
5ebf: 4a                           lsr     A                 ;divide whole thing by 4 to get 3/8
5ec0: 4a                           lsr     A
5ec1: 49 ff                        eor     #$ff              ;negate
5ec3: 38                           sec                       ;2's complement
5ec4: 65 b5                        adc     ]start_radius     ;add to start radius
5ec6: 85 9d                        sta     ]deltaX           ;save as xdelta
5ec8: c5 9e                        cmp     ]deltaY           ;have we reached ydelta?
5eca: b0 db                        bcs     :Loop             ;no, loop
5ecc: 60                           rts

                   ; 
                   ; Draws four pixels at a specified distance from the center.  Given the center
                   ; and delta X/Y in the first quadrant, this repeats the draw mirrored in the
                   ; other three quadrants.
                   ; 
                   ; We carefully avoid altering the low bit of the screen colors, so that we don't
                   ; chew up the city backdrop.  The explosion itself is drawn in color 4, which
                   ; cycles at the same rate as color 5.
                   ; 
                   ; On entry:
                   ;   $9b/9c: center X/Y
                   ;   $9d/9e: delta X/Y
                   ;   $b6: color (%PPx00000) - only two high bits matter
                   ; 
                   ]xplus          .var    $98    {addr/1}
                   ]ptr            .var    $b0    {addr/2}

5ecd: a2 00        DrawFourPix     ldx     #$00
5ecf: a5 9b                        lda     ]xc               ;compute xc + deltaX
5ed1: 18                           clc
5ed2: 65 9d                        adc     ]deltaX
5ed4: 85 b0                        sta     ]ptr              ;use as low byte of pointer
5ed6: 85 98                        sta     ]xplus            ;save for later
5ed8: a5 9c                        lda     ]yc               ;compute yc + deltaY
5eda: 18                           clc
5edb: 65 9e                        adc     ]deltaY
5edd: 49 ff                        eor     #$ff              ;invert to form high byte of pointer
5edf: 85 b1                        sta     ]ptr+1
5ee1: a1 b0                        lda     (]ptr,x)          ;get current pixel value
5ee3: 29 20                        and     #%00100000        ;strip away everything but low bit of color
5ee5: 05 b6                        ora     ]color            ;merge our color in
5ee7: 81 b0                        sta     (]ptr,x)          ;save to screen
                   ; 
5ee9: a5 9b                        lda     ]xc               ;repeat for (x - deltaX, y + deltaY)
5eeb: 38                           sec
5eec: e5 9d                        sbc     ]deltaX
5eee: 85 b0                        sta     ]ptr
5ef0: a1 b0                        lda     (]ptr,x)
5ef2: 29 20                        and     #%00100000
5ef4: 05 b6                        ora     ]color
5ef6: 81 b0                        sta     (]ptr,x)
                   ; 
5ef8: a5 9c                        lda     ]yc               ;repeat for (x - deltaX, y - deltaY)
5efa: 38                           sec
5efb: e5 9e                        sbc     ]deltaY
5efd: 49 ff                        eor     #$ff
5eff: 85 b1                        sta     ]ptr+1
5f01: a1 b0                        lda     (]ptr,x)
5f03: 29 20                        and     #%00100000
5f05: 05 b6                        ora     ]color
5f07: 81 b0                        sta     (]ptr,x)
                   ; 
5f09: a5 98                        lda     ]xplus            ;repeat for (x + deltaX, y - deltaY)
5f0b: 85 b0                        sta     ]ptr
5f0d: a1 b0                        lda     (]ptr,x)
5f0f: 29 20                        and     #%00100000
5f11: 05 b6                        ora     ]color
5f13: 81 b0                        sta     (]ptr,x)
5f15: 60                           rts

                   ; 
                   ; Show some messages before a wave starts.
                   ; 
                   ; If this is a demo rather than a game in progress, then we just came from the
                   ; title screen, so we should reset the text scrolling area.
                   ; 
                   • Clear variables

5f16: a5 93        PreWaveMsgs     lda     play_mode_flag    ;is a game in progress?
5f18: f0 1d                        beq     :NoGame           ;no, branch
5f1a: 20 48 5f                     jsr     PrintPlayerN      ;print "player N"
5f1d: a9 06                        lda     #$06
5f1f: 20 55 6a                     jsr     PrintMessagePr    ;"X POINTS"
5f22: a5 dd                        lda     score_mult        ;get the score multiplier
5f24: a2 40                        ldx     #%01000000        ;fg color = 2
5f26: 86 0b                        stx     draw_fg_color
5f28: a2 58                        ldx     #88
5f2a: a0 70                        ldy     #112
5f2c: 20 9d 6a                     jsr     DrawSmallNum      ;print multiplier
5f2f: a9 1e                        lda     #$1e
5f31: 20 55 6a                     jsr     PrintMessagePr    ;"ATARI (C)(P) 1980"
5f34: b8                           clv
5f35: 50 03                        bvc     :ShowDefMaybe

5f37: 20 33 6c     :NoGame         jsr     TxscResetStream   ;reset scrolling text
                   ; 
5f3a: a5 a7        :ShowDefMaybe   lda     wave_num          ;get current wave number
5f3c: c9 04                        cmp     #$04              ;is it >= 4?
5f3e: b0 05                        bcs     :PrintScores      ;yes, skip the "defend cities" message
5f40: a9 15                        lda     #$15
5f42: 20 51 6a                     jsr     PrintMessage      ;"DEFEND CITIES"
5f45: 4c 23 75     :PrintScores    jmp     PrintTopScores    ;print player scores and return

                   ; 
                   ; Draws "Player N" on the screen, in color #2.
                   ; 
5f48: a9 00        PrintPlayerN    lda     #$00
5f4a: 20 55 6a                     jsr     PrintMessagePr    ;"PLAYER"
5f4d: a5 b9                        lda     cur_plyr_num      ;get current player number (0/1)
5f4f: 18                           clc
5f50: 69 01                        adc     #$01              ;make it 1/2
5f52: a2 40                        ldx     #%01000000        ;fg color = 2
5f54: 86 0b                        stx     draw_fg_color
5f56: a2 9c                        ldx     #156
5f58: a0 90                        ldy     #144              ;same line as message $00
5f5a: 4c 9d 6a                     jmp     DrawSmallNum

                   ; 
                   ; Scrolls the text at the bottom of the screen one pixel left.
                   ; 
                   ; This happens on alternate frames, yielding a ~30Hz update rate.
                   ; 
5f5d: a5 ca        ScrollText      lda     game_frame_ctr    ;get the frame counter
5f5f: 4a                           lsr     A                 ;shift the low bit into the carry
5f60: b0 03                        bcs     :Return           ;if set, bail
5f62: 20 66 5f                     jsr     :DoScrollText     ;if not, scroll the text line
5f65: 60           :Return         rts

                   ; This is done with the weirdly-mapped third pixel bit.  The bottom of the
                   ; screen is drawn in the city background color (palette #1), and the scrolling
                   ; text in the general background color (palette #0), so we don't need to touch
                   ; the 2bpp pixel area at all.
                   ; 
                   ; Each byte holds 8 pixels, with the leftmost pixel in the low bit.  We don't
                   ; want to move it a full byte at a time, so we have to shift bits across 8 lines
                   ; of 32 bytes.  This is slightly complicated because each line spans 64 bytes,
                   ; using every-other byte.  Also, the leftmost pixel is in the low byte, so a
                   ; "right shift" actually moves pixels to the left.
                   ; 
                   ; Lines 223-226 start at $0401/0441/0481/04c1, and lines 227-230 start at
                   ; $0501/0541/0581/05c1.  We can access 4 lines with simple indexed addressing.
                   ; 
                   ; We could to this from right to left one line at a time, smoothly propagating
                   ; the bit down the line.  Instead, this works one column at a time, from left to
                   ; right.  This is less efficient, but is also less susceptible to tearing.
                   ; 
5f66: 20 8c 6b     :DoScrollText   jsr     UpdateScrollText  ;output new character, if it's time
5f69: a2 00                        ldx     #$00              ;start at left end
5f6b: 8a           :Loop           txa
5f6c: 29 3f                        and     #$3f              ;mod 64 (length of the line in bytes)
5f6e: c9 3e                        cmp     #$3e              ;are we at the right edge of screen?
5f70: d0 08                        bne     :NotAtEnd         ;no, branch
5f72: 38                           sec                       ;rotate a 1 bit in to use city background
5f73: 7e 01 04                     ror     VIDEO_RAM_3+$201,x ;update the last byte
5f76: 38                           sec
5f77: b8                           clv
5f78: 50 0b                        bvc     :Tail             ;go do it for the last 4 lines

5f7a: bd 03 04     :NotAtEnd       lda     VIDEO_RAM_3+$203,x ;shift bit out; this covers first 4 lines
5f7d: 4a                           lsr     A
5f7e: 7e 01 04                     ror     VIDEO_RAM_3+$201,x ;shift bit in
5f81: bd 03 05                     lda     VIDEO_RAM_3+$303,x ;shift bit out; this covers last 4 lines
5f84: 4a                           lsr     A
5f85: 7e 01 05     :Tail           ror     VIDEO_RAM_3+$301,x ;shift bit in
5f88: 8a                           txa                       ;copy index to A-reg
5f89: 18                           clc
5f8a: 69 40                        adc     #64               ;move to next line, 64 bytes down
5f8c: aa                           tax                       ;put it back in X-reg
5f8d: 90 dc                        bcc     :Loop             ;if we didn't overflow, loop
5f8f: e8                           inx                       ;advance to next byte
5f90: e8                           inx
5f91: e0 40                        cpx     #64               ;have we reached the end of the line?
5f93: 90 d6                        bcc     :Loop             ;not yet, branch
5f95: 60                           rts

                   ; 
                   ; Initializes the 3-byte BCD accumulator to zero.
                   ; 
                   ; On exit:
                   ;   $d1-d3: zero
                   ; 
5f96: a9 00        ZeroBcdAcc      lda     #$00
5f98: 85 d1                        sta     bcd3_acc
5f9a: 85 d2                        sta     bcd3_acc+1
5f9c: 85 d3                        sta     bcd3_acc+2
5f9e: 60                           rts

                   ; 
                   ; Sets the 3-byte BCD accumulator to the 2-byte argument multiplied by X.
                   ; 
                   ; On entry:
                   ;   $ba-bb: base score ($25 * score mult)
                   ;   X-reg: number of iterations + 1 (0-127)
                   ; 
                   ; On exit:
                   ;   $d1-d2: new value
                   ;   $d3: zero
                   ; 
5f9f: 20 96 5f     SetBaseScoreX   jsr     ZeroBcdAcc        ;start with zero, fall through
                   ; 
                   ; Adds a 2-byte BCD score value to the 3-byte BCD accumulator X times.
                   ; 
                   ; The base score (25 * mult) is added when you shoot down a missile, so X=0. 
                   ; Cities are worth (4 * 25 * mult), so X=3.
                   ; 
                   ; On entry:
                   ;   $ba-bb: base score ($25 * score mult)
                   ;   X-reg: number of iterations + 1 (0-127)
                   ; 
                   ; On exit:
                   ;   $d1-d3: new value + (X * old value)
                   ; 
5fa2: f8           AddBaseScoreX   sed                       ;enable decimal mode
5fa3: a5 ba        :Loop           lda     base_score_bcd    ;get the base score
5fa5: 18                           clc
5fa6: 65 d1                        adc     bcd3_acc          ;add to accumulator
5fa8: 85 d1                        sta     bcd3_acc          ;store it
5faa: a5 d2                        lda     bcd3_acc+1        ;repeat for middle byte
5fac: 65 bb                        adc     base_score_bcd+1
5fae: 85 d2                        sta     bcd3_acc+1
5fb0: a5 d3                        lda     bcd3_acc+2        ;carry into high byte
5fb2: 69 00                        adc     #$00
5fb4: 85 d3                        sta     bcd3_acc+2
5fb6: ca                           dex                       ;done yet?
5fb7: 10 ea                        bpl     :Loop             ;no, branch
5fb9: d8                           cld                       ;disable decimal mode
5fba: 60                           rts

                   ; 
                   ; Adds (5 * mult) to the BCD accumulator.
                   ; 
                   ; On entry:
                   ;   $d1-d3: current value
                   ;   $dd: multiplier
                   ; 
                   ; On exit:
                   ;   $d1-d3: += (5 * arg)
                   ; 
5fbb: a6 dd        AddBcd5x        ldx     score_mult        ;use score multiplier as counter
5fbd: f8                           sed                       ;enable decimal mode
5fbe: a9 05        :Loop           lda     #5                ;add 5 points
5fc0: 18                           clc
5fc1: 65 d1                        adc     bcd3_acc          ;add to low byte
5fc3: 85 d1                        sta     bcd3_acc
5fc5: a9 00                        lda     #$00
5fc7: 65 d2                        adc     bcd3_acc+1        ;carry into middle byte
5fc9: 85 d2                        sta     bcd3_acc+1
5fcb: a5 d3                        lda     bcd3_acc+2
5fcd: 69 00                        adc     #$00              ;carry into high byte
5fcf: 85 d3                        sta     bcd3_acc+2
5fd1: ca                           dex                       ;done yet?
5fd2: d0 ea                        bne     :Loop             ;no, loop
5fd4: d8                           cld                       ;disable decimal mode
5fd5: 60                           rts

                   ; 
                   ; Sets the current score to zero, for both players.
                   ; 
5fd6: a9 00        SetZeroScores   lda     #$00              ;set to zero
5fd8: a2 05                        ldx     #5                ;3 bytes * 2 players = 6 bytes
5fda: 9d d6 01     :Loop           sta     cur_score_lo,x
5fdd: ca                           dex                       ;done yet?
5fde: 10 fa                        bpl     :Loop             ;no, loop
5fe0: 60                           rts

                   ; 
                   ; Prints the scores for one or both players.
                   ; 
5fe1: a5 93        PrintScores     lda     play_mode_flag    ;is a game in progress?
5fe3: d0 0c                        bne     :Playing          ;yes, branch
5fe5: ad d7 01                     lda     cur_score_lo+1    ;check if player 2's score is zero
5fe8: 0d d9 01                     ora     cur_score_md+1
5feb: 0d db 01                     ora     cur_score_hi+1
5fee: b8                           clv
5fef: 50 02                        bvc     :ZeroOrOne        ;(always)

5ff1: a5 ae        :Playing        lda     num_players       ;get number of players (0/1)
5ff3: f0 05        :ZeroOrOne      beq     :NoScore2         ;1 player *or* player 2's score is zero; branch
5ff5: a0 01                        ldy     #$01
5ff7: 20 fc 5f                     jsr     PrintPlyrScore    ;print player 2's score
5ffa: a0 00        :NoScore2       ldy     #$00              ;fall through to print player 1's score
                   ; 
                   ; Print the score of player 1 or 2, identified by Y-reg (0/1).
                   ; 
5ffc: 20 96 5f     PrintPlyrScore  jsr     ZeroBcdAcc        ;zero out the accumulator
5fff: 20 13 60                     jsr     AddBcdToScore     ;copy score into BCD accumulator
6002: b9 11 60                     lda     :score_xc,y       ;get horizontal position for player N's score
6005: aa                           tax                       ;hold in X-reg
6006: a9 00                        lda     #$00
6008: 85 d4                        sta     score_dirty_flag  ;clear dirty flag
600a: a0 e2                        ldy     #226              ;position near top of screen (226-4+8=230)
600c: a5 ce                        lda     score_color       ;load color into A-reg
600e: 4c f6 6a                     jmp     PrintBcdNumber    ;print the score

                   ; 
                   ; Horizontal position for scores.
6011: 1d           :score_xc       .dd1    29                ;player 1
6012: b0                           .dd1    176               ;player 2

                   ; 
                   ; Adds the contents of the BCD accumulator to the current score, and stores the
                   ; result in both places.
                   ; 
                   ; On entry:
                   ;   Y-reg: player number (0/1)
                   ; 
                   ; On exit:
                   ;   $d4: $ff (dirty flag for score)
                   ; 
6013: a5 93        AddBcdToScore   lda     play_mode_flag    ;is a game in progress?
6015: d0 03                        bne     :NoZero           ;yes, branch
6017: 20 96 5f                     jsr     ZeroBcdAcc        ;set BCD accumulator to zero
                   ; 
601a: f8           :NoZero         sed
601b: a5 d1                        lda     bcd3_acc          ;get low byte of BCD accumulator
601d: 18                           clc
601e: 79 d6 01                     adc     cur_score_lo,y    ;add to low byte of BCD score for cur player
6021: 99 d6 01                     sta     cur_score_lo,y    ;update player's score
6024: 85 d1                        sta     bcd3_acc          ;save in acc
6026: b9 d8 01                     lda     cur_score_md,y    ;repeat for middle byte
6029: 65 d2                        adc     bcd3_acc+1
602b: 99 d8 01                     sta     cur_score_md,y
602e: 85 d2                        sta     bcd3_acc+1
6030: b9 da 01                     lda     cur_score_hi,y    ;repeat for high byte
6033: 65 d3                        adc     bcd3_acc+2
6035: 99 da 01                     sta     cur_score_hi,y
6038: 85 d3                        sta     bcd3_acc+2
                   ; 
603a: a9 ff                        lda     #$ff              ;rev2: CLD moved for code sharing
603c: 85 d4                        sta     score_dirty_flag  ;mark scores as dirty
603e: d8           :ReturnD        cld
603f: 60           :Return         rts

                   ; 
                   ; Awards bonus cities based on score.
                   ; 
                   ; Player scores are stored as a 3-byte BCD value: ABC,DEF.  Bonus cities are
                   ; awarded every XY,000 points.  We extract BC, subtract the value from the
                   ; previous time it was awarded, and then compare it to XY.
                   ; 
                   ; It's possible to earn multiple bonus cities on one wave.
                   ; 
                   ; NOTE: this is the rev3 version, which does not have the famous "810" bug found
                   ; in rev2.
                   ; 
                   ; On entry:
                   ;   X-reg=player number (0/1)
                   ; 
                   ]thousands      .var    $98    {addr/1}
                   ]bonus_city_score .var  $99    {addr/1}

6040: a5 f4        CheckBonusCity  lda     r8_irq_inv        ;get R8 switches
6042: 29 70                        and     #%01110000        ;mask to get bonus city bits
6044: 4a                           lsr     A                 ;right-shift to get 0-7
6045: 4a                           lsr     A
6046: 4a                           lsr     A
6047: 4a                           lsr     A
6048: a8                           tay
6049: b9 7b 60                     lda     bonus_city_sc_tbl,y ;get BCD score value
604c: 85 99                        sta     ]bonus_city_score ;save in ZP
604e: f0 ef                        beq     :Return           ;if it's zero, bonus cities disabled; bail
                   ; 
6050: a6 b9                        ldx     cur_plyr_num      ;get current player number (0 or 1)
6052: bd d8 01                     lda     cur_score_md,x    ;get middle BCD digits
6055: 4a                           lsr     A                 ;right-shift to reduce to 1,000s digit
6056: 4a                           lsr     A
6057: 4a                           lsr     A
6058: 4a                           lsr     A
6059: 85 98                        sta     ]thousands
605b: bd da 01                     lda     cur_score_hi,x    ;get high BCD digits
605e: 0a                           asl     A                 ;left-shift to move the 10,000s digit
605f: 0a                           asl     A
6060: 0a                           asl     A
6061: 0a                           asl     A
6062: 05 98                        ora     ]thousands        ;combine; given score xAB,xxx we now have AB
6064: f8                           sed                       ;switch to decimal mode for mod 100 arithmetic
6065: 38                           sec                       ;subtract the score from the last time we awarded;
6066: f5 c3                        sbc     bonus_award_sc,x  ; this may roll under, e.g. 4 - 90 = 14
                   ; With (current - previous) in A-reg, subtract bonus score repeatedly.  This
                   ; would fail if you could score >= 100K in one wave.
6068: 38           :BonusLoop      sec
6069: e5 99                        sbc     ]bonus_city_score ;subtract the configured bonus score
606b: 90 d1                        bcc     :ReturnD          ;rolled under, score isn't high enough yet
606d: f6 c0                        inc     num_cities,x      ;give the player a city
606f: 48                           pha                       ;push running total
                   ; 
6070: b5 c3                        lda     bonus_award_sc,x  ;get last-awarded score
6072: 18                           clc
6073: 65 99                        adc     ]bonus_city_score ;add bonus city points
6075: 95 c3                        sta     bonus_award_sc,x  ;save it
6077: 68                           pla                       ;pop running total
6078: 4c 68 60                     jmp     :BonusLoop        ;loop

                   ; 
                   ; Bonus cities are awarded every N points, based on the R8 switch setting.  The
                   ; values are stored here in BCD format.  Note switch ON=0 and OFF=1.
                   ; 
                   ;   000 - every 10,000 points
                   ;   001 - every 12,000 points
                   ;   010 - every 14,000 points
                   ;   011 - every 15,000 points
                   ;   100 - every 18,000 points
                   ;   101 - every 20,000 points
                   ;   110 - every 8,000 points
                   ;   111 - no bonus city (not in table -- uses $00 from next table)
                   ; 
                   bonus_city_sc_tbl
607b: 10 12 14 15+                 .bulk   $10,$12,$14,$15,$18,$20,$08
                   ; 
                   ; Bonus city score values, for display.  These are BCD digits to which "00" must
                   ; be appended.
6082: 00 01        bonus_city_bcd  .dd2    $0100
6084: 20 01                        .dd2    $0120
6086: 40 01                        .dd2    $0140
6088: 50 01                        .dd2    $0150
608a: 80 01                        .dd2    $0180
608c: 00 02                        .dd2    $0200
608e: 80 00                        .dd2    $0080
                   ; 
                   ; Number of missiles for wave.  19 entries.
                   ; 
                   ; The code indexes from (address - 1) because waves start at 1 rather than zero.
                   ; 
                   ; rev2 used different values for the last three entries ($60a0): $10 $12 $14
                   ; 
6090: 0c 0f 12 0c+ icbm_count_tbl  .bulk   $0c,$0f,$12,$0c,$10,$0e,$11,$0a,$0d,$10,$13,$0c,$0e,$10,$12,$0e
                                    +      $11,$13,$16
                   ; 
                   ; ICBM update frame delay.  At wave 15 the delay is set to zero, meaning the
                   ; missiles advance every frame.
                   ; 
                   ; Each entry is an 8.8 value.  The counter itself is reduced by 1 each time.  By
                   ; using a fractional value here, we can fine-tune the speed.
                   ; 
                   icbm_spd_frac_tbl
60a3: d0 e0 c0 08+                 .bulk   $d0,$e0,$c0,$08,$a0,$60,$40,$20,$10,$0a,$06,$04,$02,$01,$00
                   icbm_spd_int_tbl
60b2: 04 02 01 01+                 .bulk   $04,$02,$01,$01,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00
                   ; 
                   ; Number of smartbombs per wave.  19 entries.
                   ; 
60c1: 00 00 00 00+ sbomb_count_tbl .bulk   $00,$00,$00,$00,$00,$01,$01,$02,$03,$04,$04,$05,$05,$06,$06,$07
                                    +      $07,$07,$07
                   ; 
                   ; Flier properties, for waves 1-8.  Fliers don't appear until wave 2, so only 7
                   ; entries are needed.
                   ; 
                   ; The first table is the number of frames before the flier can fire.  The second
                   ; table is the "recharge" time before a new flier can be created.
                   ; 
                   flier_msl_delay_tbl
60d4: 80 60 40 30+                 .bulk   $80,$60,$40,$30,$20,$20,$10
                   flier_charge_int_tbl
60db: f0 a0 80 80+                 .bulk   $f0,$a0,$80,$80,$60,$40,$20
                   ; 
                   ; Horizontal positions for the 6 cities.
                   ; 
                   ; NOTE: cities are not stored left to right here.  Visually the indices are:
                   ;   ^ 3 4 0 ^ 2 1 5 ^
                   ; This is noticeable when the surviving cities are tabulated between rounds.
                   ; 
                   ; Some of the code for attacking ground targets references this with an index of
                   ; 0-8 instead of 0-5.  This table must be followed by the silo positions.
                   ; 
60e2: 5f b4 94 2c+ city_x_tbl      .bulk   $5f,$b4,$94,$2c,$47,$d0
                   ; 
                   ; Horizontal positions for the 3 ABM launcher sites.  This table must follow the
                   ; city table.
                   ; 
60e8: 14 7b f0     abm_silo_x_tbl  .bulk   $14,$7b,$f0
                   ; 
                   ; Vertical positions for the 6 cities.  They're not quite on the same line.
                   ; 
60eb: 10 15 12 12+ city_y_tbl      .bulk   $10,$15,$12,$12,$11,$11
                   ; 
                   ; Vertical positions for the 3 ABM launcher silos.  This table must follow the
                   ; city table.
                   ; 
60f1: 16 16 16     abm_silo_y_tbl  .bulk   $16,$16,$16
                   ; 
                   ; Vertical position of the top of the 3 ABM launcher silos.  This is the Y
                   ; coordinate from which the missile trails will start.
                   ; 
                   abm_silo_top_tbl
60f4: 18 18 18                     .bulk   $18,$18,$18
                   ; 
                   ; Single-bit masks.
                   ; 
60f7: 80 40 20 10+ single_bits     .bulk   $80,$40,$20,$10,$08,$04,$02,$01

                   ; 
                   ; Checks to see if we want to create a flier.  Creates one randomly if we do.
                   ; 
                   • Clear variables

                   CheckCreateFlier
60ff: a5 8d                        lda     wave_icbm_count   ;any unfired ICBMs?
6101: f0 42                        beq     :Return           ;no, bail
6103: ad 6d 01                     lda     flier_cur_y       ;do we already have an active flier?
6106: d0 3d                        bne     :Return           ;yes, bail
6108: a4 a7                        ldy     wave_num          ;get wave number in Y-reg
610a: c0 02                        cpy     #$02              ;are we on wave 1?
610c: 90 37                        bcc     :Return           ;yes, bail
610e: a5 e3                        lda     flier_charge_ctr  ;get the counter value
6110: c5 e2                        cmp     flier_charge_int  ;has it exceeded the recharge interval?
6112: 90 31                        bcc     :Return           ;not yet, bail
                   ; 
6114: a2 00                        ldx     #$00
6116: 86 c7                        stx     flier_move_ctr    ;init movement counter
6118: 86 c9                        stx     flier_fire_ctr    ;init firing counter
611a: ad 0a 40                     lda     POKEY_RANDOM      ;get random number
611d: 10 01                        bpl     :MoveRight        ;pick direction with high bit of value
611f: ca                           dex                       ;move left
6120: 86 c8        :MoveRight      stx     flier_move_dir    ;set move dir ($ff=left, $00=right)
6122: 8e 38 01                     stx     plane_cur_x       ;set horizontal position ($00=left side, $ff=right side)
6125: 29 01                        and     #%00000001        ;mask low bit of random value
6127: 85 d6                        sta     flier_type        ;use it as the flier type (0=satellite, 1=plane)
                   ; 
6129: ad 0a 40                     lda     POKEY_RANDOM      ;get random number (0-255)
612c: 4a                           lsr     A                 ;divide by 8 (now 0-31)
612d: 4a                           lsr     A
612e: 4a                           lsr     A
612f: 69 64                        adc     #100              ;add 100 (now 100-131)
6131: c0 06                        cpy     #$06              ;is wave >= 6?
6133: b0 02                        bcs     :WaveGe6          ;yes, branch
6135: 69 20                        adc     #32               ;wave 2-5, move it 32 lines higher
6137: c0 04        :WaveGe6        cpy     #$04              ;is wave >= 4?
6139: b0 02                        bcs     :WaveGe4          ;yes, branch
613b: 69 10                        adc     #16               ;wave 2-3, move it 16 lines higher
613d: 8d 6d 01     :WaveGe4        sta     flier_cur_y       ;set flier vertical position
                   ; 
6140: 20 dc 7a                     jsr     EnableFlierSound
6143: c6 de                        dec     atk_create_ctr    ;decrement attacks to make this frame
6145: 60           :Return         rts

                   ; 
                   ; Updates position of flier, if present.
                   ; 
                   ]vert_posn      .var    $b1    {addr/1}

6146: e6 e3        UpdateFlier     inc     flier_charge_ctr  ;increment recharge counter
6148: d0 02                        bne     :NotZero
614a: c6 e3                        dec     flier_charge_ctr  ;hit zero, back it off to $ff
614c: ad 6d 01     :NotZero        lda     flier_cur_y       ;get vertical position
614f: f0 29                        beq     :Return           ;if zero, filer is inactive; bail
6151: 85 b1                        sta     ]vert_posn        ;save Y coord in ZP
6153: c6 c7                        dec     flier_move_ctr    ;time to move yet?
6155: 10 23                        bpl     :Return           ;no, bail
                   ; 
6157: e6 c9                        inc     flier_fire_ctr    ;advance flier fire timer
6159: a4 d6                        ldy     flier_type        ;get unit type (0 or 1)
615b: b9 7b 61                     lda     :move_freq,y      ;get movement frequency (1 or 2)
615e: 85 c7                        sta     flier_move_ctr    ;reset movement counter
6160: ad 38 01                     lda     plane_cur_x       ;get horizontal position in A-reg
6163: 20 7d 61                     jsr     DrawFlier         ;move and redraw
6166: d0 0f                        bne     :StillVis
                   ; 
6168: a9 00                        lda     #$00              ;clear plane / satellite state
616a: 8d 6d 01                     sta     flier_cur_y       ;this marks it as inactive
616d: 85 c9                        sta     flier_fire_ctr    ;reset firing interval timer
616f: 85 e3                        sta     flier_charge_ctr  ;reset "recharge" timer
6171: 20 f8 7a                     jsr     DisableFlierSound
6174: b8                           clv
6175: 50 03                        bvc     :Return           ;(always)

6177: 8d 38 01     :StillVis       sta     plane_cur_x       ;save updated position
617a: 60           :Return         rts

617b: 01 02        :move_freq      .bulk   $01,$02           ;satellites move faster than planes

                   ; 
                   ; Draws a plane or a satellite, advancing its horizontal position.
                   ; 
                   ; On entry:
                   ;   A-reg: horizontal position
                   ;   Y-reg: shape to draw (0=satellite, 1=plane)
                   ; 
                   ; On exit:
                   ;   A-reg: horizontal position (updated)
                   ;   Z-flag zero if still on screen
                   ; 
                   ]pixel_ctr      .var    $98    {addr/1}
                   ]chunk_index    .var    $99    {addr/1}
                   ]unused_ab      .var    $ab    {addr/1}
                   ]horz_posn      .var    $b0    {addr/1}
                   ]vert_posn      .var    $b1    {addr/1}
                   ]color_index    .var    $b6    {addr/1}

617d: 84 ab        DrawFlier       sty     ]unused_ab        ;(not useful)
617f: 84 99                        sty     ]chunk_index      ;(not useful)
6181: 85 b0                        sta     ]horz_posn        ;store horizontal position in ZP
6183: a5 c8                        lda     flier_move_dir    ;get horizontal movement direction
6185: 30 05                        bmi     :MoveLeft         ;moving left, branch
6187: e6 b0                        inc     ]horz_posn        ;increment horizontal posn
6189: b8                           clv
618a: 50 02                        bvc     :Moved            ;(always)

618c: c6 b0        :MoveLeft       dec     ]horz_posn        ;decrement horizontal posn
618e: b9 ed 61     :Moved          lda     :chunk_parm_off,y ;get chunk parameter offset (3 or 5)
6191: 85 99                        sta     ]chunk_index      ;use as initial value
                   ; 
6193: a6 99        :ChunkLoop      ldx     ]chunk_index      ;get chunk index
6195: bd f5 61                     lda     :data_lengths,x   ;get length of this chunk
6198: 85 98                        sta     ]pixel_ctr        ;store in ZP
619a: bd fb 61                     lda     :colors,x         ;get color for this chunk
619d: 85 b6                        sta     ]color_index      ;store in ZP
619f: bc ef 61                     ldy     :data_offsets,x   ;get first (actually last) offset
                   ; 
61a2: a6 99        :PixelLoop      ldx     ]chunk_index      ;load this into X-reg for later
61a4: b9 63 62                     lda     :vert_offset,y    ;get vertical offset of pixel
61a7: 18                           clc
61a8: 65 b1                        adc     ]vert_posn        ;add vertical position of flier
61aa: 49 ff                        eor     #$ff              ;invert to form high byte of pointer
61ac: 85 07                        sta     gfx_ptr+1         ;save to ZP
                   ; 
61ae: b9 13 62                     lda     :horz_offset,y    ;get horizontal offset of pixel
61b1: 45 c8                        eor     flier_move_dir    ;invert (negate, mostly) if moving right
61b3: 10 0a                        bpl     :PosOff
61b5: 18                           clc                       ;negative offset
61b6: 65 b0                        adc     ]horz_posn        ;add to horizontal position
61b8: c5 b0                        cmp     ]horz_posn        ;compare to horizontal position
61ba: b0 1c                        bcs     :SkipDraw         ;if H - X is >= H, we wrapped; branch
61bc: b8                           clv
61bd: 50 07                        bvc     :CheckEdge

61bf: 18           :PosOff         clc                       ;positive offset
61c0: 65 b0                        adc     ]horz_posn        ;add to horizontal position
61c2: c5 b0                        cmp     ]horz_posn        ;compare to horizontal position
61c4: 90 12                        bcc     :SkipDraw         ;if H + X is < H, we wrapped; branch
                   ; 
61c6: dd 01 62     :CheckEdge      cmp     :horz_min,x       ;is horizontal position < min for shape?
61c9: 90 0d                        bcc     :SkipDraw         ;yes, branch
61cb: dd 07 62                     cmp     :horz_max,x       ;is horizontal position > max for shape?
61ce: b0 08                        bcs     :SkipDraw         ;yes, branch
                   ; 
61d0: 85 06                        sta     gfx_ptr           ;save horizontal position as low byte of pointer
61d2: a2 00                        ldx     #$00
61d4: a5 b6                        lda     ]color_index      ;get foreground color
61d6: 81 06                        sta     (gfx_ptr,x)       ;set pixel
61d8: c8           :SkipDraw       iny                       ;advance to next entry
61d9: c6 98                        dec     ]pixel_ctr        ;have we drawn all pixels in this chunk?
61db: 10 c5                        bpl     :PixelLoop        ;no, loop
                   ; 
61dd: a4 99                        ldy     ]chunk_index      ;get chunk index
61df: c6 99                        dec     ]chunk_index      ;decrement the ZP value
61e1: b9 0d 62                     lda     :stop_flag,y      ;was this the last chunk?
61e4: 10 ad                        bpl     :ChunkLoop        ;no, branch
                   ; All done.  Return with position in A-reg and Z-flag set.
61e6: a5 b0                        lda     ]horz_posn        ;have we reached left edge of screen?
61e8: f0 02                        beq     :Return           ;yes, return with Z-flag set
61ea: c9 ff                        cmp     #$ff              ;have we reached right edge of screen?
61ec: 60           :Return         rts

                   ; 
                   ; Shapes are drawn in multiple colors.  The leading edge is drawn in one of the
                   ; foreground colors, while the trailing edge is erased in the background color. 
                   ; The shape is never fully drawn; it just "smears" across the screen.  This
                   ; allows it to be rendered with fewer screen writes.  There's no need for an
                   ; erase function, as the exploding fireball takes care of that.
                   ; 
                   ; Each of the two shapes is drawn in multiple chunks.  Each chunk has a color
                   ; value and multiple pixels, expressed as offsets from the center position.
                   ; 
                   ; 
                   ; The first table holds the offset of the last entry in the shape chunk tables. 
                   ; This value is used as an index that is decremented.  Satellites use entries 0-
                   ; 3, planes use entries 4-5.
                   vis vis
61ed: 03 05        :chunk_parm_off .bulk   $03,$05
                   ; 
                   ; Initial offset into the vertical/horizontal coordinate tables for chunk N.
61ef: 00 17 2c 30  :data_offsets   .bulk   $00,$17,$2c,$30
61f3: 36 43                        .bulk   $36,$43
                   ; 
                   ; Number of items, minus 1, in the vertical/horizontal data tables for chunk N.
61f5: 16 14 03 05  :data_lengths   .bulk   $16,$14,$03,$05
61f9: 0c 0c                        .bulk   $0c,$0c
                   ; 
                   ; Color to draw (%PPP00000) for chunk N.
61fb: 40 00 80 e0  :colors         .bulk   $40,$00,$80,$e0   ;colors 2, 0, 4, 6
61ff: 40 00                        .bulk   $40,$00           ;colors 2, 0
                   ; 
                   ; Minimum and maximum coordinates.  There is a buffer zone at the edges of the
                   ; screen.  Normally there's enough going on that the player won't notice that
                   ; the shape isn't appearing right at the edge.
                   ; 
                   ; These are specified per-chunk, but really they're per-shape.
6201: 08 08 08 08  :horz_min       .bulk   $08,$08,$08,$08
6205: 09 09                        .bulk   $09,$09
6207: f8 f8 f8 f8  :horz_max       .bulk   $f8,$f8,$f8,$f8
620b: f7 f7                        .bulk   $f7,$f7
                   ; 
                   ; Indicates the last chunk of the shape.
620d: ff 00 00 00  :stop_flag      .bulk   $ff,$00,$00,$00
6211: ff 00                        .bulk   $ff,$00
                   ; 
                   ; Horizontal and vertical offsets for each pixel.
6213: 03 03 04 04+ :horz_offset    .bulk   $03,$03,$04,$04,$04,$03,$03,$01,$01,$05,$04,$04,$05,$fb,$fc,$fc
                                    +      $fb,$00,$00,$00,$fc,$fc,$fc,$05,$04,$03,$03,$04,$05,$fe,$fe,$f9
                                    +      $fa,$fb,$fc,$fc,$fb,$fb,$fb,$fc,$fc,$fb,$fa,$f9,$06,$06,$fa,$fa
                                    +      $02,$02,$02,$fe,$fe,$fe,$07,$05,$03,$ff,$fe,$fd,$00,$ff,$fe,$fd
                                    +      $fc,$f9,$fa,$fb,$fc,$fc,$f8,$f8,$f8,$f7,$fa,$fb,$fb,$fa,$fa,$f9
6263: 03 02 01 00+ :vert_offset    .bulk   $03,$02,$01,$00,$ff,$fe,$fd,$04,$fc,$05,$04,$fc,$fb,$05,$04,$fc
                                    +      $fb,$01,$00,$ff,$01,$00,$ff,$06,$05,$04,$fc,$fb,$fa,$04,$fc,$06
                                    +      $05,$04,$03,$02,$01,$00,$ff,$fe,$fd,$fc,$fb,$fa,$06,$fa,$06,$fa
                                    +      $01,$00,$ff,$01,$00,$ff,$00,$01,$02,$03,$04,$05,$ff,$fe,$fd,$fc
                                    +      $fb,$04,$03,$05,$04,$03,$04,$03,$02,$01,$00,$ff,$fe,$fd,$fc,$fb

                   ; 
                   ; Draws explosions on top of the "MISSILE COMMAND" title screen.  During the
                   ; game the explosions expand outward over a series of frames.  Here we just
                   ; draw/erase each explosion in one go.
                   ; 
                   ; Erasing and drawing 20 explosions takes longer than a single frame.  Because
                   ; we erase/draw each slot, rather than erasing all then drawing all, the
                   ; animation has a rippling appearance.  The color cycling makes it appear
                   ; violent.
                   ; 
                   ; This function was rewritten in rev3 to make it more compact, to make room for
                   ; the short function that follows at $6300.  The original function erased all 20
                   ; explosions before drawing 20 new ones.  Because of the color cycling, the
                   ; difference is hard to notice unless explosions overlap.  (If you see an
                   ; "eclipse", where an explosion is partly blacked out, you know you have a rev3
                   ; machine.)
                   ; 
                   • Clear variables
                   ]end_radius     .var    $9a    {addr/1}
                   ]xc             .var    $9b    {addr/1}
                   ]yc             .var    $9c    {addr/1}
                   ]start_radius   .var    $b5    {addr/1}

62b3: a2 13        DrawTitleExpl   ldx     #19               ;20 explosions
62b5: a9 00        :ExplLoop       lda     #$00
62b7: 85 9a                        sta     ]end_radius       ;prep to erase (shrink to zero)
62b9: bd 6e 01                     lda     expl_ypos,x       ;is there an explosion in this slot?
62bc: f0 06                        beq     :CreateNew        ;no, branch
62be: bd c2 01                     lda     expl_radius_idx_arr,x ;get radius
62c1: 20 f1 62                     jsr     :DrawExpl         ;call the draw routine to erase it
                   ; 
62c4: a9 fc        :CreateNew      lda     #%11111100        ;cycle all colors except 0/1 (background)
62c6: 85 ec                        sta     color_cyc_mask
62c8: ad 0a 40                     lda     POKEY_RANDOM      ;get random number
62cb: 29 07                        and     #%00000111        ;reduce to 0-7
62cd: 09 04                        ora     #%00000100        ;make it 4-7
62cf: 9d c2 01                     sta     expl_radius_idx_arr,x ;use as radius
62d2: 85 9a                        sta     ]end_radius
                   ; 
62d4: ad 0a 40                     lda     POKEY_RANDOM      ;get random number
62d7: 9d 39 01                     sta     expl_xpos,x       ;save as X position
62da: ad 0a 40     :Retry          lda     POKEY_RANDOM      ;get random number
62dd: c9 d0                        cmp     #208
62df: b0 f9                        bcs     :Retry            ;too high, loop
62e1: c9 38                        cmp     #56
62e3: 90 f5                        bcc     :Retry            ;too low, loop
62e5: 9d 6e 01                     sta     expl_ypos,x       ;save as Y position
                   ; 
62e8: a9 00                        lda     #$00              ;initial radius is zero
62ea: 20 f1 62                     jsr     :DrawExpl         ;draw it
62ed: ca                           dex                       ;done yet?
62ee: 10 c5                        bpl     :ExplLoop         ;no, loop
62f0: 60                           rts

62f1: 85 b5        :DrawExpl       sta     ]start_radius     ;save A-reg as starting radius
62f3: bd 6e 01                     lda     expl_ypos,x       ;get XC/YC from arrays
62f6: 85 9c                        sta     ]yc
62f8: bd 39 01                     lda     expl_xpos,x
62fb: 85 9b                        sta     ]xc
62fd: 4c 71 5e                     jmp     DrawExplosion     ;draw the explosion

                   ; 
                   ; Clamps the wave number to [0,39].
                   ; 
                   ; This is a patch added in rev3, to prevent the weird stuff that happened on
                   ; higher-numbered waves.  Simply changing 40 to 20 works because the intensity
                   ; peaks at wave 19, and the color scheme repeats every 20 waves.
                   ; 
6300: a5 a7        ClampWaveNum    lda     wave_num          ;get the current wave number
6302: c9 28                        cmp     #40               ;have we reached wave 40?
6304: 90 02                        bcc     :Lt40             ;not yet, branch
6306: a9 14                        lda     #20               ;reset to 20
6308: 85 a7        :Lt40           sta     wave_num
630a: 4c 86 69                     jmp     ClearScreen       ;this patch replaced a call to ClearScreen

                   ; 
                   ; Updates the position of a smartbomb.
                   ; 
                   ; Bombs move in a direct path like missiles unless explosions are detected
                   ; nearby.
                   ; 
630d: 20 4d 63     MoveSmartBomb   jsr     ScanForExpl       ;look for nearby explosions
6310: a5 d5                        lda     sbomb_prox        ;any flags set?
6312: d0 06                        bne     :Traffic          ;yes, branch
6314: 20 11 54                     jsr     MoveMissile       ;no, just move it like a missile
6317: b8                           clv
6318: 50 32                        bvc     :Return           ;(always)

631a: c9 ff        :Traffic        cmp     #$ff              ;totally surrounded?
631c: d0 06                        bne     :Dodge            ;no, try to dodge
631e: 20 11 54                     jsr     MoveMissile       ;yes, just make a run for it
6321: b8                           clv
6322: 50 28                        bvc     :Return           ;(always)

6324: 20 8c 63     :Dodge          jsr     CalcDesiredDir    ;calculate desired angle (0-15)
6327: a5 d5                        lda     sbomb_prox        ;get collision map
6329: a4 a7                        ldy     wave_num          ;get current wave number
632b: c0 09                        cpy     #$09              ;>= 9?
632d: b0 02                        bcs     :Wave9            ;yes, branch
632f: 09 01                        ora     #$01              ;no, set a bit indicating an explosion above
6331: 85 d5        :Wave9          sta     sbomb_prox        ; so bomb won't try to move upward
                   ; 
6333: 20 00 64                     jsr     CalcEvasiveMove1  ;find an evasive move
6336: d0 06                        bne     :TrySingle        ;no good, branch
6338: 20 5d 64                     jsr     DoEvasiveMove     ;move evasively
633b: b8                           clv
633c: 50 0e                        bvc     :Return           ;(always)

633e: 20 2d 64     :TrySingle      jsr     CalcEvasiveMove2  ;find an evasive move
6341: d0 06                        bne     :NoEvade          ;no good, branch
6343: 20 5d 64                     jsr     DoEvasiveMove     ;move evasively
6346: b8                           clv
6347: 50 03                        bvc     :Return           ;(always)

6349: 20 11 54     :NoEvade        jsr     MoveMissile
634c: 60           :Return         rts

                   ; 
                   ; Reads pixels from the screen to identify nearby explosions.  Sets bits in a
                   ; byte to identify which directions are obstructed.
                   ; 
                   ; This can trigger on the target 'X' drawn when an ABM is fired, which is either
                   ; a false-positive or a very prescient smart bomb.  They can also false-positive
                   ; on passing missile heads.
                   ; 
                   ; On exit:
                   ;   $d5: proximal explosion flags
                   ;   (X-reg preserved)
                   ; 
                   • Clear variables
                   ]saved_x        .var    $ac    {addr/1}
                   ]pix_ptr        .var    $b0    {addr/2}

634d: a9 00        ScanForExpl     lda     #$00
634f: 85 d5                        sta     sbomb_prox        ;init result to zero
6351: 86 ac                        stx     ]saved_x          ;preserve X-reg
6353: a0 07                        ldy     #$07              ;8 locations in coord table
6355: a6 97        :Loop           ldx     slot_index        ;get smartbomb slot index (8-15)
6357: bd 5d 01                     lda     abm_cur_y,x       ;get current Y coordinate
635a: 18                           clc
635b: 79 82 63                     adc     :peek_tbl,y       ;add peek offset
635e: 49 ff                        eor     #$ff              ;invert to form pointer
6360: 85 b1                        sta     ]pix_ptr+1        ;set as high byte
6362: bd 28 01                     lda     abm_cur_x,x       ;get current X coordinate
6365: 18                           clc
6366: 79 84 63                     adc     :peek_tbl+2,y     ;add peek offset
6369: 85 b0                        sta     ]pix_ptr          ;set as low byte of pointer
                   ; 
636b: a2 00                        ldx     #$00
636d: a1 b0                        lda     (]pix_ptr,x)      ;get pixel color
636f: 29 c0                        and     #%11000000        ;mask off low bits to ignore city bkgnd
6371: c9 80                        cmp     #%10000000        ;is it 4 (or 5), the explosion color?
6373: d0 07                        bne     :NotExpl          ;no, branch
                   ; 
6375: a5 d5                        lda     sbomb_prox
6377: 19 f8 63                     ora     single_bits_rev,y ;set the bit for this entry
637a: 85 d5                        sta     sbomb_prox
637c: 88           :NotExpl        dey                       ;done with all positions?
637d: 10 d6                        bpl     :Loop             ;no, loop
637f: a6 ac                        ldx     ]saved_x          ;restore X-reg
6381: 60                           rts

                   ; 
                   ; Coordinate offset table.  Used to peek at the pixels near the smartbomb.
                   ; 
                   ; The X/Y coordinates overlap, using an arrangement similar to a cos/sin table
                   ; (cos is 1/4 phase ahead).  The set of pixels examined forms a roughly diamond
                   ; shape, starting on the N corner and moving counter-clockwise:
                   ; 
                   ;          (0,8)                    0
                   ;    (-6,6)    (6,6)              1   7
                   ;  (-8,0)         (8,0)  bit#:  2       6
                   ;    (-6,-6)   (6,-6)             3   5
                   ;          (0,-8)                   4
                   ; 
                   ; The smartbomb itself is a diamond with radius 3 (5 pixels across), so this
                   ; gives us a 6-pixel early collison warning.
                   ; 
6382: 08 06 00 fa+ :peek_tbl       .bulk   $08,$06,$00,$fa,$f8,$fa,$00,$06,$08,$06

                   ; 
                   ; Computes the desired angle of travel for a smartbomb.
                   ; 
                   ; The result should be 0-15, but the code doesn't clamp the result.  That value
                   ; is only possible for vertical upward movement, and smartbombs don't move that
                   ; way, so it shouldn't be an issue.
                   ; 
                   ; On exit:
                   ;   $d0: angle (0-16)
                   ; 
                   • Clear variables
                   ]slope_adj      .var    $98    {addr/1}
                   ]result_int     .var    $a9    {addr/1}
                   ]result_frac    .var    $aa    {addr/1}
                   ]deltaX         .var    $b0    {addr/1}
                   ]deltaY         .var    $b1    {addr/1}

638c: a4 97        CalcDesiredDir  ldy     slot_index        ;get smartbomb slot index (8-15)
638e: 20 f6 58                     jsr     CalcMissilePath   ;calculate path from current position
6391: a4 b0                        ldy     ]deltaX
6393: a5 b1                        lda     ]deltaY
6395: d0 05                        bne     :NotHoriz         ;not horizontal move, branch
6397: a9 04                        lda     #$04              ;max out for horizontal
6399: b8                           clv                       ;(note: no divide-by-zero check)
639a: 50 16                        bvc     Set98             ;(always)

639c: 20 bb 59     :NotHoriz       jsr     Divide8_8         ;compute deltaY / deltaX (slope)
639f: a0 04                        ldy     #$04              ;compare to 5 entries
63a1: a5 a9        :Loop           lda     ]result_int
63a3: d9 de 63                     cmp     :incr_int,y       ;does the integer part match?
63a6: d0 05                        bne     :NotEqual         ;no, branch to test for < or >
63a8: a5 aa                        lda     ]result_frac      ;get fractional part of quotient
63aa: d9 e3 63                     cmp     :incr_frac,y      ;compare to increment
63ad: 88           :NotEqual       dey                       ;move to next entry
63ae: 90 f1                        bcc     :Loop             ;while quotient < table value, loop
63b0: c8                           iny                       ;undo last DEY (Y-reg now 0-4)
63b1: 98                           tya
63b2: 85 98        Set98           sta     ]slope_adj        ;0-4
                   ; 
63b4: a6 97                        ldx     slot_index        ;get slot index
63b6: bd 10 06                     lda     msl_inc_xint,x    ;EOR sign bits for X and Y increments
63b9: 5d 30 06                     eor     msl_inc_yint,x
63bc: 10 07                        bpl     :SameSign         ;if signs match, branch
63be: a9 04                        lda     #$04              ;set direction to (4 - direction) since
63c0: 38                           sec                       ; angle increases other way
63c1: e5 98                        sbc     ]slope_adj
63c3: 85 98                        sta     ]slope_adj
                   ; 
63c5: bd 10 06     :SameSign       lda     msl_inc_xint,x    ;get the X increment
63c8: 2a                           rol     A                 ;roll sign bit into carry
63c9: bd 30 06                     lda     msl_inc_yint,x    ;get the Y increment
63cc: 2a                           rol     A                 ;roll sign bit into carry, X sign into low bit
63cd: 2a                           rol     A                 ;signs are now in low bits: %??????XY
63ce: 29 03                        and     #%00000011        ;mask to get %000000XY
63d0: a8                           tay                       ;use as index
63d1: b9 da 63                     lda     :move_dir,y       ;get the base offset from the table
63d4: 18                           clc
63d5: 65 98                        adc     ]slope_adj        ;add the slope offset (0-4) to get 0-16
63d7: 85 d0                        sta     sbomb_dir         ; (bug? should clamp to 0-15?)
63d9: 60                           rts

                   ; 
                   ; Values used to form a direction index, based on the sign bits of the path to
                   ; the target.  Smart bombs should be moving downward (-Y).  The above routine
                   ; combines these values with the slope index (0-4).
                   ; 
                   ; %00 = +X/+Y -> $0c-0f
                   ; %01 = +X/-Y -> $08-0b
                   ; %10 = -X/+Y -> $00-03
                   ; %11 = -X/-Y -> $04-07
                   ; 
                   ;        0
                   ;    1-3 | d-f
                   ;   4 ---*--- c
                   ;    5-7 | 9-b
                   ;        8
                   ; 
                   ; For example, a smartbomb dropping from left to right might have deltaX=44
                   ; deltaY=97, yielding a slope of $0234 (2.203).  That's greater than entry #3 in
                   ; the table below, so we start with 3.  It has +X/-Y, so we change it to (4-3) =
                   ; 1.  Combining the sign bits yields %01, so we get entry #1 below, which is
                   ; $08.  Add the two to get the direction: $08 + $01 = $09.
                   ; 
                   ; As the slope gets closer to horizontal, we increase toward $0b.  If the bomb
                   ; were moving right to left, a near-vertical drop would be $07, and as the slope
                   ; got closer to horizontal we would decrease toward $04.
63da: 0c 08 00 04  :move_dir       .bulk   $0c,$08,$00,$04
                   ; 
                   ; Slope index thresholds.  Entry 0 is for a very shallow angle (near
                   ; horizontal), entry 4 is very steep (near vertical).
                   ; 
63de: 00 00 00 01+ :incr_int       .bulk   $00,$00,$00,$01,$05
63e3: 00 32 ab 7f+ :incr_frac      .bulk   $00,$32,$ab,$7f,$06
                   ; 
                   ; This table is indexed with the smartbomb direction (0-15) formed earlier.  The
                   ; bit patterns are compared to the result from the proximity detection routine;
                   ; see $6382 for the table, which uses a table arranged like this:
                   ; 
                   ;       0
                   ;     1   7
                   ;   2       6
                   ;     3   5
                   ;       4
                   ; 
                   ; For example, if there are explosions at angles 4/5/6, we generated a collision
                   ; mask %01110000, and need to find an entry in the table below that masks off
                   ; all the 1 bits.  Entries 0-4 work, so we want to use whichever is closest to
                   ; the desired travel direction.  The entry number (0-15) indicates the direction
                   ; the smartbomb should move.  (If you look at the comment above $63da, you'll
                   ; see that directions 0-4 are moving up and to the left, directly away from the
                   ; explosions in this example.)
63e8: 83           evade_mask_tbl  .dd1    %10000011
63e9: 87                           .dd1    %10000111
63ea: 07                           .dd1    %00000111
63eb: 0f                           .dd1    %00001111
63ec: 0e                           .dd1    %00001110
63ed: 1e                           .dd1    %00011110
63ee: 1c                           .dd1    %00011100
63ef: 3c                           .dd1    %00111100
63f0: 38                           .dd1    %00111000
63f1: 78                           .dd1    %01111000
63f2: 70                           .dd1    %01110000
63f3: f0                           .dd1    %11110000
63f4: e0                           .dd1    %11100000
63f5: e1                           .dd1    %11100001
63f6: c1                           .dd1    %11000001
63f7: c3                           .dd1    %11000011
                   ; 
                   ; Like single_bits at $60f7, but in the opposite order.  (This is arguably the
                   ; correct order, since table entry 0 holds bit 0, unless you subscribe to the
                   ; PowerPC documentation style.)
                   ; 
63f8: 01 02 04 08+ single_bits_rev .bulk   $01,$02,$04,$08,$10,$20,$40,$80

                   ; 
                   ; Find the evasive path that is closest to the direction we want to go.  We
                   ; start with the smartbomb's current direction and probe in both directions.  At
                   ; each step we check multiple collision bits to try to find the ideal path.
                   ; 
                   ; On entry:
                   ;   $d0: smartbomb's desired direction index
                   ;   $d5: explosion proximity detection results
                   ; 
                   ; On exit:
                   ;   Y-reg: evasive move index
                   ;   Z-flag: set if move found
                   ; 
                   ]counter        .var    $98    {addr/1}

                   CalcEvasiveMove1
6400: a4 d0                        ldy     sbomb_dir         ;get movement direction index
6402: a6 d0                        ldx     sbomb_dir         ;in both X-reg and Y-reg
6404: a9 08                        lda     #$08              ;check 9 angles
6406: 85 98                        sta     ]counter
6408: a5 d5        :Loop1          lda     sbomb_prox        ;get results from proximity test
640a: 39 e8 63                     and     evade_mask_tbl,y  ;mask bits with table
640d: d0 01                        bne     :NotY             ;one or more bits still set, branch
640f: 60                           rts                       ;return with Y-reg result, Z-flag set

6410: a5 d5        :NotY           lda     sbomb_prox        ;get proximity flags
6412: 3d e8 63                     and     evade_mask_tbl,x  ;mask bits with table
6415: d0 05                        bne     :NotX             ;one or more bits still set, branch
6417: 8a                           txa                       ;want value in X-reg, so transfer that to Y-reg
6418: a8                           tay                       ; to use as function result
6419: a9 00                        lda     #$00              ;set Z-flag
641b: 60                           rts                       ;return with Z-flag set

641c: 88           :NotX           dey                       ;move Y-reg one step clockwise
641d: 10 02                        bpl     :Ge0              ;branch if still > 0
641f: a0 0f                        ldy     #15               ;reset to 15
6421: e8           :Ge0            inx                       ;move X-reg index one step counter-clockwise
6422: e0 10                        cpx     #16               ;reached 16?
6424: 90 02                        bcc     :Sub16            ;no, branch
6426: a2 00                        ldx     #$00              ;reset to 0
6428: c6 98        :Sub16          dec     ]counter          ;have we considered all possible angles?
642a: 10 dc                        bpl     :Loop1            ;not yet, loop
642c: 60                           rts                       ;return with Z-flag clear (counter went negative)

                   ; 
                   ; Selects an evasive path if the previous function wasn't able to.  We test
                   ; single bits in the proximity bit map to find an explosion-free direction that
                   ; is closest to our desired angle.
                   ; 
                   ; On entry:
                   ;   $d0: smartbomb's desired direction index
                   ;   $d5: explosion proximity detection results
                   ; 
                   ; On exit:
                   ;   Y-reg: evasive move index
                   ;   Z-flag: set if move found
                   ; 
                   CalcEvasiveMove2
642d: a5 d0                        lda     sbomb_dir         ;get desired direction (0-15)
642f: 4a                           lsr     A                 ;divide by 2
6430: a8                           tay                       ;copy to X-reg and Y-reg
6431: aa                           tax
6432: a9 04                        lda     #$04              ;walk through 5 directions
6434: 85 98                        sta     ]counter
                   ; 
6436: a5 d5        :Loop           lda     sbomb_prox        ;get proximity flags
6438: 39 f8 63                     and     single_bits_rev,y ;is there an explosion in the Y-reg direction?
643b: f0 09                        beq     :MoveY            ;no, move that way
643d: a5 d5                        lda     sbomb_prox        ;get proximity flags
643f: 3d f8 63                     and     single_bits_rev,x ;is there an explosion in the X-reg direction?
6442: d0 08                        bne     :NoMove           ;yes, branch
                   ; 
6444: 8a                           txa                       ;copy X-reg to Y-reg
6445: a8                           tay                       ; through the A-reg
6446: 98           :MoveY          tya                       ;copy Y-reg to A-reg
6447: 0a                           asl     A                 ;double it
6448: a8                           tay                       ;back to Y-reg to use as result
6449: a9 00                        lda     #$00              ;set Z-flag
644b: 60                           rts

644c: 88           :NoMove         dey                       ;move Y-reg one step clockwise
644d: 10 02                        bpl     :Ge0              ;branch if still >= 0
644f: a0 07                        ldy     #$07              ;reset to 7
6451: e8           :Ge0            inx                       ;move X-reg one step counter-clockwise
6452: e0 08                        cpx     #$08              ;reached 8?
6454: 90 02                        bcc     :Le8              ;no, branch
6456: a2 00                        ldx     #$00              ;reset to 0
6458: c6 98        :Le8            dec     ]counter          ;have we considered all possible angles?
645a: 10 da                        bpl     :Loop             ;not yet, loop
645c: 60                           rts                       ;return with Z-flag clear (counter went negative)

                   ; 
                   ; Moves a smartbomb in an evasive path.
                   ; 
                   ; If we're too close to the edge of the screen or to the cities, we just do a
                   ; direct move instead.
                   ; 
                   ; On entry:
                   ;   Y-reg: evasive move index
                   ; 
645d: a6 97        DoEvasiveMove   ldx     slot_index        ;get smartbomb slot index (8-15)
645f: bd 28 01                     lda     abm_cur_x,x       ;get current X coord
6462: c9 f7                        cmp     #$f7              ;near right edge of screen?
6464: b0 19                        bcs     :Jmp_MoveMissile  ;yes, branch
6466: c9 08                        cmp     #$08              ;near left edge of screen?
6468: 90 15                        bcc     :Jmp_MoveMissile  ;yes, branch
646a: bd 5d 01                     lda     abm_cur_y,x       ;get current Y coord
646d: c9 ce                        cmp     #206              ;near top of screen?
646f: b0 0e                        bcs     :Jmp_MoveMissile  ;yes, branch
6471: c9 2d                        cmp     #45               ;near bottom of screen?
6473: 90 0a                        bcc     :Jmp_MoveMissile  ;yes, branch
6475: 20 82 64                     jsr     :EvasiveMove      ;move the bomb in the requested direction
6478: a4 97                        ldy     slot_index        ;get the slot index
647a: 20 f6 58                     jsr     CalcMissilePath   ;recalculate direct path to target for next frame
647d: 18                           clc
647e: 60                           rts

                   :Jmp_MoveMissile
647f: 4c 11 54                     jmp     MoveMissile       ;move directly toward target

6482: a6 97        :EvasiveMove    ldx     slot_index        ;get smartbomb slot index (8-15)
6484: b9 af 64                     lda     :evade_frac+4,y   ;get fractional part of X move
6487: 18                           clc
6488: 7d 18 01                     adc     abm_cur_xfrac,x   ;add to current position
648b: 9d 18 01                     sta     abm_cur_xfrac,x
648e: b9 c3 64                     lda     :evade_int+4,y    ;get int part of X move
6491: 7d 28 01                     adc     abm_cur_x,x       ;add to current position
6494: 9d 28 01                     sta     abm_cur_x,x
                   ; 
6497: b9 ab 64                     lda     :evade_frac,y     ;get fractional part of Y move
649a: 18                           clc
649b: 7d 4d 01                     adc     abm_cur_yfrac,x   ;add to current position
649e: 9d 4d 01                     sta     abm_cur_yfrac,x
64a1: b9 bf 64                     lda     :evade_int,y      ;get int part of Y move
64a4: 7d 5d 01                     adc     abm_cur_y,x       ;add to current position
64a7: 9d 5d 01                     sta     abm_cur_y,x
64aa: 60                           rts

                   ; 
                   ; Smartbomb evasive movement tables.  The evasive move direction index (0-15)
                   ; determines which entry to use.  Each entry has 8.8 movement increments for X
                   ; and Y.
                   ; 
                   ; The X and Y values overlap with an offset of 4 (like a cos/sin table with a
                   ; quarter-phase offset).
                   ; 
                   ; #    X     Y       X       Y
                   ; 0: $0000,$0100   0.000, +1.000
                   ; 1: $ff9e,$00ec  -0.383, +0.922
                   ; 2: $ff4c,$00b4  -0.703, +0.703
                   ; 3: $ff14,$0062  -0.922, +0.383
                   ; 4: $ff00,$0000  -1.000,  0.000
                   ; 5: $ff14,$ff9e  -0.922, -0.383
                   ; 6: $ff4c,$ff4c  -0.703, -0.703
                   ; 7: $ff9e,$ff14  -0.383, -0.922
                   ; 8: $0000,$ff00   0.000, -1.000
                   ; 9: $0062,$ff14  +0.383, -0.922
                   ; a: $00b4,$ff4c  +0.703, -0.703
                   ; b: $00ec,$ff9e  +0.922, -0.383
                   ; c: $0100,$0000  +1.000,  0.000
                   ; d: $00ec,$0062  +0.922, +0.383
                   ; e: $00b4,$00b4  +0.703, +0.703
                   ; f: $0062,$00ec  +0.383, +0.922
                   ; 
                   ; This uses the same counter-clockwise angle system seen in earlier tables:
                   ;       0
                   ;     2   e
                   ;   4       c
                   ;     6   a
                   ;       8
64ab: 00 ec b4 62  :evade_frac     .bulk   $00,$ec,$b4,$62   ;Y starts here
64af: 00 9e 4c 14                  .bulk   $00,$9e,$4c,$14   ;X starts here
64b3: 00 14 4c 9e                  .bulk   $00,$14,$4c,$9e
64b7: 00 62 b4 ec                  .bulk   $00,$62,$b4,$ec
64bb: 00 ec b4 62                  .bulk   $00,$ec,$b4,$62
64bf: 01 00 00 00  :evade_int      .bulk   $01,$00,$00,$00
64c3: 00 ff ff ff                  .bulk   $00,$ff,$ff,$ff
64c7: ff ff ff ff                  .bulk   $ff,$ff,$ff,$ff
64cb: 00 00 00 00                  .bulk   $00,$00,$00,$00
64cf: 01 00 00 00                  .bulk   $01,$00,$00,$00

                   ; 
                   ; Draws crosshairs.  The previous contents of the screen that were drawn over by
                   ; the previous crosshairs are restored, then the crosshairs are drawn in the new
                   ; position.
                   ; 
                   ; The code uses two pointers, one to walk the vertical line and one to walk the
                   ; horizontal line.  The center pixel is saved and drawn twice.
                   ; 
                   ; On entry:
                   ;   $09-0a: previously-drawn crosshair X/Y position
                   ;   $a5-a6: current crosshair X/Y position
                   ; 
                   ; On exit:
                   ;   $09-0a: updated to $a5-a6
                   ; 
                   ]new_v_ptr      .var    $00    {addr/2}
                   ]last_h_ptr     .var    $03    {addr/2}
                   ]last_v_ptr     .var    $06    {addr/2}
                   ]new_h_ptr      .var    $0d    {addr/2}
                   ]color          .var    $98    {addr/1}

64d3: a9 e0        DrawCrosshairs  lda     #%11100000        ;color = 7
64d5: 4c da 64                     jmp     DoDrawCrosshair

                   ; 
                   ; Draws crosshairs in the background color.
                   ; 
                   ; This isn't about "erasing" them -- we do that every time as part of drawing --
                   ; but rather making them disappear by drawing them in the background color.
                   ; 
64d8: a9 00        ClearCrosshairs lda     #$00              ;color = 0
64da: 85 98        DoDrawCrosshair sta     ]color            ;save color in ZP
64dc: a5 0a                        lda     last_cross_yc     ;get vertical position (zero if crosshairs hidden)
64de: c9 18                        cmp     #24               ;was it visible before?
64e0: 90 78                        bcc     :Return           ;no, bail
64e2: 18                           clc
64e3: 69 03                        adc     #$03              ;add 3 to get top row
64e5: 49 ff                        eor     #$ff              ;invert to form high byte of pointer
64e7: 85 07                        sta     ]last_v_ptr+1     ;save it to ZP
64e9: a5 09                        lda     last_cross_xc     ;get horizontal position
64eb: 85 06                        sta     ]last_v_ptr       ;use as low byte of pointer
64ed: 38                           sec
64ee: e9 03                        sbc     #$03              ;subtract 3 to get left column
64f0: 85 03                        sta     ]last_h_ptr       ;save as low byte of pointer
64f2: a5 0a                        lda     last_cross_yc     ;get middle row
64f4: 49 ff                        eor     #$ff              ;invert to form high byte of pointer
64f6: 85 04                        sta     ]last_h_ptr+1     ;save to ZP
                   ; 
64f8: a5 a6                        lda     cross_yc          ;get current crosshair Y coordinate
64fa: c9 18                        cmp     #24               ;is it actually drawable?
64fc: 90 5c                        bcc     :Return           ;no, bail
64fe: 85 0a                        sta     last_cross_yc     ;save for next time
6500: 18                           clc
6501: 69 03                        adc     #$03              ;add 3 to get top row
6503: 49 ff                        eor     #$ff              ;invert to form high byte of pointer
6505: 85 01                        sta     ]new_v_ptr+1      ;save to ZP
6507: a5 a5                        lda     cross_xc          ;use X coord as low byte of pointer
6509: 85 09                        sta     last_cross_xc
650b: 85 00                        sta     ]new_v_ptr
650d: 38                           sec
650e: e9 03                        sbc     #$03              ;subtract 3 to get left edge
6510: 85 0d                        sta     ]new_h_ptr        ;use as low byte of pointer
6512: a5 a6                        lda     cross_yc          ;get Y coord
6514: 49 ff                        eor     #$ff              ;invert to form high byte of pointer
6516: 85 0e                        sta     ]new_h_ptr+1      ;save to ZP
                   ; We now have four pointers: old horizontal, old vertical, new horizontal, new
                   ; vertical.  Walk through the old pointers, restoring the previous pixels if
                   ; they weren't overwritten.
6518: a0 06                        ldy     #6                ;crosshairs are 7 pixels high/wide
651a: a2 00                        ldx     #$00
651c: a1 03        :RestoreLoop    lda     (]last_h_ptr,x)   ;get pixel value from screen (horz bar)
651e: 29 e0                        and     #%11100000        ;mask the color bits
6520: c9 e0                        cmp     #%11100000        ;is it color 7?
6522: d0 05                        bne     :NoCopyH          ;no, something overdrew crosshair pixel; branch
6524: b9 16 00                     lda     cross_back_store+7,y ;get previous pixel value
6527: 81 03                        sta     (]last_h_ptr,x)   ;write to display
6529: a1 06        :NoCopyH        lda     (]last_v_ptr,x)   ;get pixel value from screen (vert bar)
652b: 29 e0                        and     #%11100000        ;mask the color bits
652d: c9 e0                        cmp     #%11100000        ;is it color 7?
652f: d0 05                        bne     :NoCopyV          ;no, something overdrew crosshair pixel; branch
6531: b9 0f 00                     lda     cross_back_store,y ;get previous pixel value
6534: 81 06                        sta     (]last_v_ptr,x)   ;write to display
6536: e6 03        :NoCopyV        inc     ]last_h_ptr       ;move right one column
6538: e6 07                        inc     ]last_v_ptr+1     ;move down one row
653a: 88                           dey                       ;are we done yet?
653b: 10 df                        bpl     :RestoreLoop      ;no, loop
                   ; Now draw the new crosshairs, preserving the background.  We want to
                   ; preserve/draw in the opposite order in which we restore so that the center
                   ; pixel is handled correctly.
653d: a0 06                        ldy     #6                ;crosshairs are 7 pixels high/wide
653f: a2 00                        ldx     #$00
6541: a1 00        :DrawLoop       lda     (]new_v_ptr,x)    ;get pixel value from screen (vert bar)
6543: 99 0f 00                     sta     cross_back_store,y ;save it
6546: a5 98                        lda     ]color
6548: 81 00                        sta     (]new_v_ptr,x)    ;write the color to the screen
654a: e6 01                        inc     ]new_v_ptr+1      ;move down one row
654c: a1 0d                        lda     (]new_h_ptr,x)    ;get pixel value from screen (horz bar)
654e: 99 16 00                     sta     cross_back_store+7,y ;save it
6551: a5 98                        lda     ]color
6553: 81 0d                        sta     (]new_h_ptr,x)    ;write the color to the screen
6555: e6 0d                        inc     ]new_h_ptr        ;move right one column
6557: 88                           dey                       ;are we done yet?
6558: 10 e7                        bpl     :DrawLoop         ;no, loop
655a: 60           :Return         rts

655b: 16                           .dd1    $16               ;checksum byte

                   ; 
                   ; Initializes the crosshairs, placing them in a low-center position. 
                   ; Initializes the backing store to the background color.
                   ; 
655c: a9 80        InitCrosshairs  lda     #128              ;set XC=128 (center of screen)
655e: 85 09                        sta     last_cross_xc
6560: 85 a5                        sta     cross_xc
6562: a9 50                        lda     #80               ;set YC=80 (lower third of screen)
6564: 85 0a                        sta     last_cross_yc
6566: 85 a6                        sta     cross_yc
                   ; 
6568: a9 00                        lda     #$00              ;color = 0 (background)
656a: a2 0d                        ldx     #13               ;7 vertical entries, 7 horizontal entries
656c: 95 0f        :Loop           sta     cross_back_store,x ;init backing store
656e: ca                           dex
656f: 10 fb                        bpl     :Loop
6571: 60                           rts

                   ; 
                   ; Hides the crosshairs.  Draws them in black, then sets the current position to
                   ; Y=0 so we won't try to draw them.
                   ; 
6572: 20 d8 64     HideCrosshairs  jsr     ClearCrosshairs   ;erase crosshairs from screen
6575: a9 00                        lda     #$00
6577: 85 a6                        sta     cross_yc          ;set current/previous positions to zero
6579: 85 0a                        sta     last_cross_yc     ; to indicate that crosshairs are hidden
657b: 60                           rts

                   ; 
                   ; Draws the flashing 'X' that marks an ABM's destination when the player fires.
                   ; 
                   ; On exit:
                   ;   (X-reg preserved)
                   ; 
                   • Clear variables
                   ]ptr1           .var    $00    {addr/2}
                   ]ptr2           .var    $06    {addr/2}
                   ]saved_x        .var    $23    {addr/1}

657c: 86 23        DrawTargetX     stx     ]saved_x          ;preserve X-reg
657e: a9 80                        lda     #%10000000        ;fg color = 4 (cycles)
6580: 85 0b                        sta     draw_fg_color
6582: a5 a6                        lda     cross_yc          ;get target vertical position
6584: 49 ff                        eor     #$ff              ;invert to form pointer
6586: 38                           sec
6587: e9 02                        sbc     #$02              ;subtract 2 to get top line
6589: 85 07                        sta     ]ptr2+1           ;set high bytes of pointers
658b: 85 01                        sta     ]ptr1+1
658d: a5 a5                        lda     cross_xc          ;get target horizontal position
658f: 4c a9 65                     jmp     DoDrawTargetX     ;jump to common code

                   ; 
                   ; Erases the 'X' that marks the ABM's destination when the player fires.  This
                   ; is called before drawing the explosion.
                   ; 
                   ; On entry:
                   ;   $97: index of ABM
                   ; 
                   ; On exit:
                   ;   (X-reg preserved)
                   ; 
                   ]counter        .var    $98    {addr/1}

6592: 86 23        EraseTargetX    stx     ]saved_x          ;save X-reg
6594: a9 00                        lda     #$00
6596: 85 0b                        sta     draw_fg_color     ;fg color = index 0 (background)
6598: a6 97                        ldx     slot_index        ;get index of interest
659a: bd b2 01                     lda     abm_targ_y_arr,x  ;get vertical coordinate
659d: 49 ff                        eor     #$ff              ;invert bits to form high byte of pointer
659f: 38                           sec
65a0: e9 02                        sbc     #$02              ;subtract 2 to get top line
65a2: 85 07                        sta     ]ptr2+1
65a4: 85 01                        sta     ]ptr1+1
65a6: bd a2 01                     lda     abm_targ_x_arr,x  ;get horizontal coordinate
                   ; 
65a9: 18           DoDrawTargetX   clc
65aa: 69 02                        adc     #$02              ;add 2 to get right side
65ac: 85 06                        sta     ]ptr2             ;use as low byte of pointer
65ae: 38                           sec
65af: e9 04                        sbc     #$04              ;subtract 4 (i.e. original coord - 2) to get left
65b1: 85 00                        sta     ]ptr1             ;use as low byte of pointer
                   ; 
65b3: a2 00                        ldx     #$00              ;want X-reg=0 for (ZP,X)
65b5: a9 04                        lda     #$04              ;plot 5 points
65b7: 85 98                        sta     ]counter
65b9: a5 0b                        lda     draw_fg_color     ;get the color
65bb: 81 06        :Loop           sta     (]ptr2,x)         ;set a pixel on one diagonal
65bd: 81 00                        sta     (]ptr1,x)         ;set a pixel on the other diagonal
65bf: e6 07                        inc     ]ptr2+1           ;move down one line
65c1: c6 06                        dec     ]ptr2             ;move one pixel left
65c3: e6 01                        inc     ]ptr1+1           ;move down one line
65c5: e6 00                        inc     ]ptr1             ;move one pixel right
65c7: c6 98                        dec     ]counter          ;done yet?
65c9: 10 f0                        bpl     :Loop             ;no, branch
                   ; 
65cb: a6 23                        ldx     ]saved_x          ;restore X-reg
65cd: 60                           rts

                   ; 
                   ; Draws the player indicator glyph, next to the score.  This flashes about once
                   ; a second during play.  We also erase the glyph for the inactive player.
                   ; 
65ce: a0 e0        DrawPlyrIndic   ldy     #%11100000        ;fg color = 7
65d0: a5 b9        DrawPlyrIndicC  lda     cur_plyr_num      ;get current player number (0 or 1)
65d2: 20 db 65                     jsr     :DoDraw           ;draw it
65d5: a0 00                        ldy     #$00              ;fg color = 0 (background)
65d7: a5 b9                        lda     cur_plyr_num      ;get player number
65d9: 49 01                        eor     #$01              ;flip it to the other player
65db: 84 0b        :DoDraw         sty     draw_fg_color     ;set color
65dd: aa                           tax
65de: bd ec 65                     lda     :glyph_index_tbl,x ;get index of arrow glyph
65e1: 48                           pha
65e2: bd ee 65                     lda     :horz_posn_tbl,x  ;get horizontal position
65e5: aa                           tax
65e6: a0 e2                        ldy     #226              ;set vertical position at the top
65e8: 68                           pla
65e9: 4c 00 66                     jmp     DrawLetterByIdx   ;draw it

                   :glyph_index_tbl
65ec: 21                           .dd1    $21               ;left arrow glyph
65ed: 22                           .dd1    $22               ;right arrow glyph
65ee: 4d           :horz_posn_tbl  .dd1    77
65ef: a8                           .dd1    168

                   ; 
                   ; Draws or erases the 1UP / 2UP arrows next to the player scores.
                   ; 
65f0: a5 d7        UpdatePlyrIndic lda     draw_1up_flag     ;are we in the "show" or "hide" phase?
65f2: d0 06                        bne     :Erase            ;hide, draw in background color
65f4: 20 ce 65                     jsr     DrawPlyrIndic     ;show, draw it
65f7: b8                           clv
65f8: 50 05                        bvc     :Return           ;(always)

65fa: a0 00        :Erase          ldy     #%00000000        ;fg color = 0 (background)
65fc: 20 d0 65                     jsr     DrawPlyrIndicC
65ff: 60           :Return         rts

                   ; 
                   ; Draws the specified letter, by index.  The index is converted to an offset
                   ; into the A-Z glyph set.
                   ; 
                   ; Sometimes this is called with indices outside the range of letters, e.g.
                   ; $21/22.  This works because the letter set is followed in memory by the symbol
                   ; set.
                   ; 
                   ; On entry:
                   ;   A-reg: letter index
                   ;   X-reg: horizontal position
                   ;   Y-reg: vertical position
                   ;   $08: preserve background flag
                   ;   $0b: foreground color index (%PPP00000)
                   ;   $0c: background color index (%PPP00000)
                   ; 
                   • Clear variables
                   ]xpos           .var    $00    {addr/1}
                   ]ypos           .var    $01    {addr/1}
                   ]glyph_ptr      .var    $0d    {addr/2}
                   ]vert_scale     .var    $21    {addr/1}
                   ]horz_scale     .var    $22    {addr/1}

6600: 85 0d        DrawLetterByIdx sta     ]glyph_ptr        ;init pointer with glyph index
6602: 86 00                        stx     ]xpos             ;copy args to ZP
6604: 84 01                        sty     ]ypos
6606: a9 00                        lda     #$00
6608: 85 0e                        sta     ]glyph_ptr+1      ;set high byte to zero
660a: a9 ff                        lda     #$ff
660c: 85 08                        sta     preserve_bg_flag  ;default to preserving the background pixels
660e: 06 0d                        asl     ]glyph_ptr        ;multiply glyph index by 8
6610: 26 0e                        rol     ]glyph_ptr+1
6612: 06 0d                        asl     ]glyph_ptr
6614: 26 0e                        rol     ]glyph_ptr+1
6616: 06 0d                        asl     ]glyph_ptr
6618: 26 0e                        rol     ]glyph_ptr+1
661a: a9 62                        lda     #<glyphs_2        ;add to glyph base address to form pointer
661c: 18                           clc
661d: 65 0d                        adc     ]glyph_ptr
661f: 85 0d                        sta     ]glyph_ptr
6621: a9 73                        lda     #>glyphs_2
6623: 65 0e                        adc     ]glyph_ptr+1
6625: 85 0e                        sta     ]glyph_ptr+1      ;(fall through to draw code)
                   ; 
                   ; Draws the glyph at the pointer.  The scale is set to 1x1.
                   ; 
                   ; On entry:
                   ;   $00: center X (will have 4 subtracted)
                   ;   $01: center Y (will have 4 added)
                   ;   $08: preserve background flag
                   ;   $0b: foreground color index (%PPP00000)
                   ;   $0c: background color index (%PPP00000)
                   ;   $0d-0e: glyph pointer
                   ; 
                   DrawGlyphPtrNoSc
6627: a9 01                        lda     #$01              ;set scale to 1x1
6629: 85 22                        sta     ]horz_scale
662b: 85 21                        sta     ]vert_scale
                   ; 
                   ; Draws an 8x8 1-bit bitmap.  This is generally used for character glyphs, but
                   ; is also used for the city bitmaps and a few arrows.
                   ; 
                   ; The parameters determine the position, scale factor, colors, and whether the
                   ; transparent parts are drawn in the background color or the existing pixels are
                   ; preserved.
                   ; 
                   ; On entry:
                   ;   $00: center X (will have 4 subtracted)
                   ;   $01: center Y (will have 4 added)
                   ;   $08: preserve background flag
                   ;   $0b: foreground color index (%PPP00000)
                   ;   $0c: background color index (%PPP00000)
                   ;   $0d-0e: glyph pointer
                   ;   $21: vertical glyph size multiplier
                   ;   $22: horizontal glyph size multiplier
                   ; 
                   ]horz_counter   .var    $03    {addr/1}
                   ]vert_counter   .var    $04    {addr/1}
                   ]bit_counter    .var    $98    {addr/1}

662d: a0 07        DrawGlyphPtr    ldy     #$07              ;start with the last byte in the 8-byte glyph
662f: a5 01                        lda     ]ypos             ;add 4 to Y to get top pixel
6631: 18                           clc
6632: 69 04                        adc     #$04
6634: 49 ff                        eor     #$ff              ;invert to get the high byte of the address
6636: 85 07                        sta     gfx_ptr+1         ;save it
6638: a5 21        :ByteLoop       lda     ]vert_scale       ;get vertical scale multiplier
663a: 85 04                        sta     ]vert_counter     ;use as counter
663c: a5 00        :LineLoop       lda     ]xpos             ;subtract 4 from X to get left pixel
663e: 38                           sec
663f: e9 04                        sbc     #$04
6641: 24 fc                        bit     cocktail_flip     ;are we flipped?
6643: 50 02                        bvc     :NoFlip           ;no, branch
6645: 49 ff                        eor     #$ff              ;invert the coordinate
6647: 85 06        :NoFlip         sta     gfx_ptr           ;set as low byte of pointer
                   ; 
6649: a2 00                        ldx     #$00              ;set X-reg=0 and leave it that way
664b: a9 07                        lda     #$07              ;byte holds data for 8 pixels
664d: 85 98                        sta     ]bit_counter
664f: b1 0d                        lda     (]glyph_ptr),y    ;get byte from glyph data
6651: 0a           :BitLoop        asl     A                 ;shift high (leftmost) bit into carry
6652: 48                           pha                       ;save pixel data
6653: a5 22                        lda     ]horz_scale       ;get horizontal scale multiplier
6655: 85 03                        sta     ]horz_counter     ;use as counter
6657: a5 0b                        lda     draw_fg_color     ;load the foreground color
6659: b0 0b                        bcs     :DrawPixel        ;if bit set, draw it
665b: a5 08                        lda     preserve_bg_flag  ;bit clear; draw or preserve background?
665d: 10 05                        bpl     :UseBgColor       ;branch to draw the background color
665f: a1 06                        lda     (gfx_ptr,x)       ;get the existing color value from the screen
6661: b8                           clv                       ;(why not just skip the STA entirely?)
6662: 50 02                        bvc     :DrawPixel        ;(always)

6664: a5 0c        :UseBgColor     lda     draw_bg_color     ;use the background color
6666: 81 06        :DrawPixel      sta     (gfx_ptr,x)       ;write the color value to the pixel
                   ; 
6668: 24 fc                        bit     cocktail_flip     ;is the screen flipped?
666a: 50 05                        bvc     :NoFlip           ;no, branch
666c: c6 06                        dec     gfx_ptr           ;move one pixel right, in reverse
666e: b8                           clv
666f: 50 02                        bvc     :Next             ;(always)

6671: e6 06        :NoFlip         inc     gfx_ptr           ;move one pixel right
6673: c6 03        :Next           dec     ]horz_counter     ;decrement horz scale counter; are we done?
6675: d0 ef                        bne     :DrawPixel        ;not yet, loop (w/o fetching screen pix?)
6677: 68                           pla                       ;restore shifted pixel data
6678: c6 98                        dec     ]bit_counter      ;have we processed all the bits?
667a: 10 d5                        bpl     :BitLoop          ;not yet, branch
                   ; 
667c: e6 07                        inc     gfx_ptr+1         ;move to next line
667e: c6 04                        dec     ]vert_counter     ;decrement scale counter; are we done?
6680: d0 ba                        bne     :LineLoop         ;not yet, loop
6682: 88                           dey                       ;advance to next byte of glyph data
6683: 10 b3                        bpl     :ByteLoop         ;loop
6685: 60                           rts

                   ; 
                   ; Erases missile trails when the missile explodes.  Used for both ICBMs and
                   ; ABMs.
                   ; 
                   ; On entry:
                   ;   $97: index of missile (0-15)
                   ; 
                   ; On exit:
                   ;   (X/Y-reg preserved)
                   ; 
                   • Clear variables
                   ]xint           .var    $00    {addr/1}
                   ]yint           .var    $01    {addr/1}
                   ]last_line_addr .var    $02    {addr/1}
                   ]xfrac          .var    $03    {addr/1}
                   ]yfrac          .var    $04    {addr/1}
                   ]saved_x        .var    $23    {addr/1}
                   ]saved_y        .var    $24    {addr/1}
                   ]xcounter       .var    $98    {addr/1}
                   ]ycounter       .var    $99    {addr/1}

6686: 86 23        EraseTrail      stx     ]saved_x          ;preserve X/Y-reg
6688: 84 24                        sty     ]saved_y
668a: a4 97                        ldy     slot_index        ;get missile slot index
668c: a9 00                        lda     #$00              ;init counters
668e: 85 98                        sta     ]xcounter         ;(the fractional part of the increment will be
6690: a9 ff                        lda     #$ff              ; added to or subtracted from these)
6692: 85 99                        sta     ]ycounter
                   ; 
6694: b9 92 01                     lda     abm_start_y_arr,y ;get start Y coord
6697: 49 ff                        eor     #$ff              ;invert to form pointer
6699: 85 07                        sta     gfx_ptr+1         ;set as high byte
669b: b9 82 01                     lda     abm_start_x_arr,y ;get start X coord
669e: 85 06                        sta     gfx_ptr           ;set as low byte
                   ; 
66a0: b9 00 06                     lda     msl_inc_xfrac,y   ;copy increments to ZP
66a3: 85 03                        sta     ]xfrac
66a5: b9 20 06                     lda     msl_inc_yfrac,y
66a8: 85 04                        sta     ]yfrac
66aa: b9 10 06                     lda     msl_inc_xint,y
66ad: 85 00                        sta     ]xint
66af: b9 30 06                     lda     msl_inc_yint,y
66b2: 85 01                        sta     ]yint
                   ; 
66b4: b9 5d 01                     lda     abm_cur_y,y       ;get current Y coord
66b7: 49 ff                        eor     #$ff              ;invert so we can compare directly to pointer
66b9: 85 02                        sta     ]last_line_addr   ;save in ZP
66bb: c5 07                        cmp     gfx_ptr+1         ;does start == end?
66bd: f0 2c                        beq     :Bail             ;yes, nothing to do; bail
                   ; 
66bf: a2 00                        ldx     #$00
66c1: a9 00        :Loop           lda     #$00              ;color = 0 (background)
66c3: 81 06                        sta     (gfx_ptr,x)       ;set pixels
                   ; 
66c5: a5 98                        lda     ]xcounter         ;update the fractional counter for X
66c7: 18                           clc
66c8: 65 03                        adc     ]xfrac
66ca: 85 98                        sta     ]xcounter
66cc: a5 06                        lda     gfx_ptr           ;if the add carried, update the pointer
66ce: 65 00                        adc     ]xint
66d0: 85 06                        sta     gfx_ptr
                   ; 
66d2: a5 99                        lda     ]ycounter         ;update the fractional counter for Y
66d4: 38                           sec                       ;subtract rather than add because we're incrementing
66d5: e5 04                        sbc     ]yfrac            ; the pointer, not the coordinate, and pointers
66d7: 85 99                        sta     ]ycounter         ; increase as they move down rather than up
66d9: a5 07                        lda     gfx_ptr+1         ;if the sub borrowed, update the pointer
66db: e5 01                        sbc     ]yint
66dd: 85 07                        sta     gfx_ptr+1
66df: c9 18                        cmp     #$18              ;are we < $1800 (off top or bottom of screen)?
66e1: 90 08                        bcc     :Bail             ;yes, bail (bug? test should be #$19?)
66e3: c5 02                        cmp     ]last_line_addr   ;have we reached the final Y-coordinate (inclusive)?
66e5: d0 da                        bne     :Loop             ;no, branch
                   ; 
66e7: a9 00                        lda     #$00              ;reached the end, erase last pixel
66e9: 81 06                        sta     (gfx_ptr,x)
66eb: a6 23        :Bail           ldx     ]saved_x          ;restore X/Y-reg
66ed: a4 24                        ldy     ]saved_y
66ef: 60                           rts

                   ; 
                   ; Draws the head of a missile (ABM or ICBM).
                   ; 
                   ; On entry:
                   ;   $97: missile slot index (0-15)
                   ; 
66f0: a4 97        DrawMslHead     ldy     slot_index
66f2: a9 80                        lda     #%10000000        ;color = 4 (cycles)
66f4: 4c 01 67                     jmp     DoDrawTrail

                   ; 
                   ; Draws one pixel of a missile's trail (ABM or ICBM).
                   ; 
                   ; On entry:
                   ;   $97: missile slot index (0-15)
                   ; 
66f7: a9 e0        DrawMslTrail    lda     #%11100000        ;color = 7 (ABM / outgoing)
66f9: a4 97                        ldy     slot_index        ;check slot number
66fb: c0 08                        cpy     #$08              ;is it in the ABM range?
66fd: 90 02                        bcc     DoDrawTrail       ;yes, branch
66ff: a9 40                        lda     #%01000000        ;color = 2 (ICBM / incoming)
                   ; 
                   ; Missile slot index in Y-reg, color in A-reg.
                   ; 
                   ]saved_x        .var    $98    {addr/1}

6701: 85 0b        DoDrawTrail     sta     draw_fg_color     ;set foreground color
6703: 86 98                        stx     ]saved_x          ;preserve X-reg
6705: b9 28 01                     lda     abm_cur_x,y       ;get X coordinate
6708: 85 06                        sta     gfx_ptr           ;use as low byte of pointer
670a: b9 5d 01                     lda     abm_cur_y,y       ;get Y coordinate
670d: 49 ff                        eor     #$ff              ;invert to form high byte of pointer
670f: 85 07                        sta     gfx_ptr+1
6711: a5 0b                        lda     draw_fg_color     ;get color
6713: a2 00                        ldx     #$00
6715: 81 06                        sta     (gfx_ptr,x)       ;set pixel
6717: a6 98                        ldx     ]saved_x          ;restore X-reg
6719: 60                           rts

                   ; 
                   ; Prepares for the next incoming wave.  Erases the screen, draws the city
                   ; background and cities, sets the color palette, and updates the screen flip.
                   ; 
671a: a2 07        PrepWaveScreen  ldx     #$07              ;color palette has 8 entries
671c: a9 0e                        lda     #%00001110        ;black
671e: 95 e4        :Loop           sta     color_palette,x   ;set the color
6720: ca                           dex
6721: 10 fb                        bpl     :Loop
                   ; 
6723: 20 00 63                     jsr     ClampWaveNum      ;rev2: JSR $6986 (clear screen) instead
6726: 20 7c 68                     jsr     DrawCityBkgnd     ;draw the city backdrop
6729: 20 5e 67                     jsr     DrawCities        ;draw the cities
672c: 20 21 69                     jsr     SetColorPalette   ;set the color palette for this wave
                   ; 
                   ; Configures a pointer to the language-specific string table, and initializes
                   ; the cocktail / flip value.
                   ; 
                   ; On exit:
                   ;   $2a-2b: points to start of locale-specific string table
                   ;   $fc: set to $00 (upright), $80 (cocktail + plyr 1) or $ff (cocktail + plyr
                   ; 2)
                   ; 
672f: a5 f3        InitLangAndFlip lda     r10_irq_mod       ;get R10 switches
6731: 4a                           lsr     A
6732: 4a                           lsr     A
6733: 4a                           lsr     A
6734: 4a                           lsr     A                 ;shift language into bits 2/1
6735: 29 06                        and     #%00000110        ;mask off everything else
6737: a8                           tay                       ;use as index
6738: b9 95 6a                     lda     msg_lang_tbl,y    ;copy to ZP pointer
673b: 85 2a                        sta     msg_base_ptr
673d: b9 96 6a                     lda     msg_lang_tbl+1,y
6740: 85 2b                        sta     msg_base_ptr+1
                   ; 
6742: a9 00                        lda     #$00              ;start with zero
6744: a4 f4                        ldy     r8_irq_inv        ;get R8 switches
6746: 10 08                        bpl     :Upright          ;not cocktail cabinet, branch
6748: 09 80                        ora     #%10000000        ;set high bit
674a: a4 b9                        ldy     cur_plyr_num      ;get player number (0/1)
674c: f0 02                        beq     :Upright          ;if player 1, branch
674e: 09 7f                        ora     #%01111111        ;player 2, set all bits (esp. bit 6 for BIT/BVC)
6750: 85 fc        :Upright        sta     cocktail_flip     ;note this feeds back into OUT0 during IRQ
6752: 60                           rts

                   ; 
                   ; Erases a city bitmap, drawing it with the background color.
                   ; 
                   ; On entry:
                   ;   Y-reg: city index (0-5)
                   ; 
6753: be e2 60     EraseCityNoFlip ldx     city_x_tbl,y      ;get X coordinate of city center
6756: b9 eb 60                     lda     city_y_tbl,y      ;get Y coordinate of city center
6759: a8                           tay
675a: a9 00                        lda     #$00              ;color = 0/0
675c: f0 21                        beq     DrawCityNoFlip    ;(always)

                   ; 
                   ; Draws all live cities.
                   ; 
                   • Clear variables
                   ]counter        .var    $99    {addr/1}

675e: a0 05        DrawCities      ldy     #$05              ;max of 6 cities
6760: 84 99                        sty     ]counter
6762: a4 b9        :CityLoop       ldy     cur_plyr_num      ;get current player number
6764: b9 c5 00                     lda     live_city_mask,y  ;get bit mask of live cities
6767: a4 99                        ldy     ]counter          ;get bit to test
6769: 39 f7 60                     and     single_bits,y     ;get Nth bit (actually 2 ^ (7-N))
676c: f0 0c                        beq     :Destroyed        ;bit not set, city is dead
                   ; 
676e: be e2 60                     ldx     city_x_tbl,y      ;get X coordinate of city center
6771: b9 eb 60                     lda     city_y_tbl,y      ;get Y coordinate of city center
6774: a8                           tay
6775: a9 e6                        lda     #%11100110        ;color = 3/7
6777: 20 7f 67                     jsr     DrawCityNoFlip    ;draw the city
677a: c6 99        :Destroyed      dec     ]counter          ;done yet?
677c: 10 e4                        bpl     :CityLoop         ;no, branch
677e: 60                           rts

                   ; 
                   ; Draws a city, suppressing the horizontal screen flip.
                   ; 
                   ; City positions aren't flipped horizontally when the screen is rotated, so we
                   ; want to disable that for the call.
                   ; 
                   ; On entry:
                   ;   X-reg: X coordinate
                   ;   Y-reg: Y coordinate
                   ;   A-reg: two city color indices, as %AAA0BBB0
                   ; 
677f: 48           DrawCityNoFlip  pha                       ;save A-reg (holds the colors)
6780: a5 fc                        lda     cocktail_flip
6782: 29 bf                        and     #%10111111        ;clear the overflow (V) bit to disable flip
6784: 85 fc                        sta     cocktail_flip
6786: 68                           pla                       ;restore A-reg
6787: 20 95 67                     jsr     DrawCity          ;draw city with glyph renderer
678a: a5 fc                        lda     cocktail_flip
678c: 29 3f                        and     #%00111111        ;mask off cocktail / flip bits
678e: f0 04                        beq     :Return           ;none of the low bits were set, bail
6790: 09 c0                        ora     #%11000000        ;yes, re-enable flip
6792: 85 fc                        sta     cocktail_flip     ;(even if player 1 is active... bug?)
6794: 60           :Return         rts

                   ; 
                   ; Draws a city at the specified coordinates.
                   ; 
                   ; Uses the character glyph renderer to draw two pairs of overlapping 8x8 bitmaps
                   ; in two different colors.  This is used to draw the inter-wave bonus city tally
                   ; as well as the game-play cities.
                   ; 
                   ; On entry:
                   ;   X-reg: horizontal position
                   ;   Y-reg: vertical position
                   ;   A-reg: two city color indices, as %AAA0BBB0
                   ; 
                   ]counter        .var    $05    {addr/1}
                   ]xc             .var    $1d    {addr/1}
                   ]yc             .var    $1e    {addr/1}
                   ]colors         .var    $1f    {addr/2}

6795: 86 1d        DrawCity        stx     ]xc
6797: 84 1e                        sty     ]yc
6799: 85 1f                        sta     ]colors           ;save color #0 (only the high 3 bits count)
679b: 0a                           asl     A                 ;shift low nibble of color into high
679c: 0a                           asl     A
679d: 0a                           asl     A
679e: 0a                           asl     A
679f: 85 20                        sta     ]colors+1         ;save color #1
67a1: a2 03                        ldx     #$03              ;4 glyphs to draw
67a3: 86 05                        stx     ]counter
                   ; 
67a5: a6 05        :Loop           ldx     ]counter
67a7: bc c5 67                     ldy     :city_color_idx,x
67aa: b9 1f 00                     lda     $001f,y           ;[colors]
67ad: 85 0b                        sta     draw_fg_color
                   ; 
67af: a4 1e                        ldy     ]yc               ;get vertical coord in Y-reg
67b1: bd c9 67                     lda     :city_color_glyph,x ;get the glyph index
67b4: 48                           pha
67b5: a5 1d                        lda     ]xc               ;get horizontal coordinate
67b7: 18                           clc
67b8: 7d cd 67                     adc     :city_x_offset,x  ;adjust by table offset
67bb: aa                           tax                       ;move to X-reg
67bc: 68                           pla                       ;put the fg color in A-reg
67bd: 20 00 66                     jsr     DrawLetterByIdx   ;draw the glyph
67c0: c6 05                        dec     ]counter          ;done yet?
67c2: 10 e1                        bpl     :Loop             ;no, loop
67c4: 60                           rts

                   ; 
                   ; City rendering data.  The code above draws 4 glyphs.  The data below specifies
                   ; the glyph index, color index, and horizontal offset for each part.
                   ; 
67c5: 01 00 01 00  :city_color_idx .bulk   $01,$00,$01,$00   ;index into colors stored at $1f
                   :city_color_glyph
67c9: 1e 1d 20 1f                  .bulk   $1e,$1d,$20,$1f   ;index, past letters into symbols
67cd: fc fc 04 04  :city_x_offset  .bulk   $fc,$fc,$04,$04   ;+4/-4

                   ; 
                   ; Erases an ABM icon (ammo indicator) from a silo.
                   ; 
                   ; On entry:
                   ;   Y-reg: silo index (0-2)
                   ; 
                   ]ptr            .var    $06    {addr/2}

67d1: be a0 00     EraseAmmo       ldx:    abms_left,y       ;get number of ABMs remaining (0-9)
67d4: e8                           inx                       ;add one to get index of ABM that was just fired
67d5: a9 20                        lda     #%00100000        ;color = 1 (city-area background)
                   ; 
                   ; Draws an ABM icon (ammo indicator).
                   ; 
                   ; On entry:
                   ;   Y-reg: silo index (0-2)
                   ;   X-reg: ABM index (1-10)
                   ;   A-reg: color (%PPP00000)
                   ; 
                   ; On exit:
                   ;   (Y-reg preserved)
                   ; 
67d7: 18           DrawAmmo1       clc
67d8: 48                           pha                       ;preserve A-reg
67d9: b9 e8 60                     lda     abm_silo_x_tbl,y  ;get horizontal position of launcher
67dc: 7d 19 68                     adc     abm_xoffset_tbl-1,x ;add position of icon
67df: 85 06                        sta     ]ptr              ;use as low byte of pointer
67e1: 18                           clc
67e2: b9 f1 60                     lda     abm_silo_y_tbl,y  ;get vertical position of launcher
67e5: 7d 0f 68                     adc     abm_yoffset_tbl-1,x ;add position of icon
67e8: 49 ff                        eor     #$ff              ;invert to form high byte of address
67ea: 85 07                        sta     ]ptr+1            ;use as high byte of pointer
67ec: 68                           pla                       ;restore A-reg
                   ; 
                   ; Draws an ABM ammo icon.
                   ; 
                   ; The pattern is hard-coded into the instruction stream:
                   ; 
                   ;    #
                   ;    #
                   ;    #
                   ;   ###
                   ;   # #
                   ; 
                   ; On entry:
                   ;   $06-07: pointer to top-center pixel
                   ;   A-reg: color (%PPP00000)
                   ; 
67ed: a2 00        DrawAmmoIcon    ldx     #$00
67ef: 81 06                        sta     (]ptr,x)          ;manipulate the pointer directly to draw pixels
67f1: e6 07                        inc     ]ptr+1
67f3: 81 06                        sta     (]ptr,x)
67f5: e6 07                        inc     ]ptr+1
67f7: 81 06                        sta     (]ptr,x)
67f9: e6 07                        inc     ]ptr+1
67fb: 81 06                        sta     (]ptr,x)
67fd: c6 06                        dec     ]ptr
67ff: 81 06                        sta     (]ptr,x)
6801: e6 07                        inc     ]ptr+1
6803: 81 06                        sta     (]ptr,x)
6805: e6 06                        inc     ]ptr
6807: e6 06                        inc     ]ptr
6809: 81 06                        sta     (]ptr,x)
680b: c6 07                        dec     ]ptr+1
680d: 81 06                        sta     (]ptr,x)
680f: 60                           rts

                   ; 
                   ; There are 10 ABMs at each launcher site.  As they are fired, the visual
                   ; representation is updated in this order:
                   ;        9
                   ;      8   7
                   ;    6   5   4
                   ;  1   3   2   0
                   ; 
6810: 02 ff ff fc+ abm_yoffset_tbl .bulk   $02,$ff,$ff,$fc,$fc,$fc,$f9,$f9,$f9,$f9
681a: 00 fd 03 fa+ abm_xoffset_tbl .bulk   $00,$fd,$03,$fa,$00,$06,$fd,$03,$f7,$09

                   ; 
                   ; Prints "LOW" below a missile launcher silo.
                   ; 
                   ; On entry:
                   ;   A-reg: silo index (0-2)
                   ; 
                   ; On exit:
                   ;   (X-reg / Y-reg preserved)
                   ; 
                   • Clear variables
                   ]saved_a        .var    $02    {addr/1}
                   ]saved_x        .var    $23    {addr/1}
                   ]saved_y        .var    $24    {addr/1}

                   PrintLauncherLow
6824: 86 23                        stx     ]saved_x          ;save X-reg / Y-reg
6826: 84 24                        sty     ]saved_y
6828: aa                           tax                       ;put silo index in X-reg
6829: bd 61 68                     lda     low_msg_tbl,x     ;get "LOW" message index for this launcher
682c: 24 fc                        bit     cocktail_flip     ;are we flipped?
682e: 50 03                        bvc     :NoFlip           ;no, branch
6830: bd 67 68                     lda     low_msg_tbl_r,x   ;use other table
6833: 4c 56 68     :NoFlip         jmp     :Print

                   ; 
                   ; Prints "OUT" below a missile launcher silo.  The previous "LOW" indicator is
                   ; erased first.
                   ; 
                   ; On entry:
                   ;   A-reg: launcher index (0-2)
                   ; 
                   ; On exit:
                   ;   (X-reg / Y-reg preserved)
                   ; 
                   PrintLauncherOut
6836: 85 02                        sta     ]saved_a          ;preserve A/X/Y-reg
6838: 86 23                        stx     ]saved_x
683a: 84 24                        sty     ]saved_y
683c: aa                           tax                       ;put silo index in X-reg
683d: bd 61 68                     lda     low_msg_tbl,x     ;get "LOW" message index for this launcher
6840: 24 fc                        bit     cocktail_flip     ;screen flipped?
6842: 50 03                        bvc     :NoFlip           ;no, branch
6844: bd 67 68                     lda     low_msg_tbl_r,x   ;yes, use alternate table
6847: 20 4d 6a     :NoFlip         jsr     EraseMessage      ;erase "LOW"
684a: a6 02                        ldx     ]saved_a          ;get launcher index
684c: bd 5e 68                     lda     out_msg_tbl,x     ;get "OUT" message index for this launcher
684f: 24 fc                        bit     cocktail_flip     ;screen flipped?
6851: 50 03                        bvc     :Print            ;no, branch
6853: bd 64 68                     lda     out_msg_tbl_r,x   ;yes, use alternate table
6856: 20 55 6a     :Print          jsr     PrintMessagePr    ;print message
6859: a6 23                        ldx     ]saved_x          ;restore X-reg / Y-reg
685b: a4 24                        ldy     ]saved_y
685d: 60                           rts

685e: 0f           out_msg_tbl     .dd1    $0f               ;"OUT" (left)
685f: 10                           .dd1    $10               ;"OUT" (middle)
6860: 11                           .dd1    $11               ;"OUT" (right)
6861: 16           low_msg_tbl     .dd1    $16               ;"LOW" (left)
6862: 17                           .dd1    $17               ;"LOW" (middle)
6863: 18                           .dd1    $18               ;"LOW" (right)
6864: 11           out_msg_tbl_r   .dd1    $11               ;"OUT" (right)
6865: 10                           .dd1    $10               ;"OUT" (middle)
6866: 0f                           .dd1    $0f               ;"OUT" (left)
6867: 18           low_msg_tbl_r   .dd1    $18               ;"LOW" (right)
6868: 17                           .dd1    $17               ;"LOW" (middle)
6869: 16                           .dd1    $16               ;"LOW (left)

                   ; 
                   ; Draws all of the ammo icons for one silo.
                   ; 
                   ; On entry:
                   ;   Y-reg: silo number (0-2)
                   ; 
                   ]counter        .var    $02    {addr/1}

686a: be a0 00     DrawAllAmmo     ldx:    abms_left,y       ;get number of ABMs left at this silo
686d: f0 0c                        beq     :Return           ;if zero, bail
686f: 86 02        :Loop           stx     ]counter          ;save to ZP as counter
6871: a9 e0                        lda     #%11100000        ;color = 7
6873: 20 d7 67                     jsr     DrawAmmo1         ;draw ammo icon
6876: a6 02                        ldx     ]counter
6878: ca                           dex                       ;done yet?
6879: d0 f4                        bne     :Loop             ;no, loop
687b: 60           :Return         rts

                   ; 
                   ; Draws the terrain that forms the background for the cities and for the text at
                   ; the bottom of the screen.  This is drawn with color index 1.
                   ; 
                   ; Note this is NOT flipped when the screen is rotated.
                   ; 
                   • Clear variables

687c: a2 00        DrawCityBkgnd   ldx     #$00              ;set to zero (does not change)
687e: a0 00                        ldy     #$00              ;offset into data
6880: a9 e6                        lda     #$e6              ;set pointer to line #25 ($e600)
6882: 85 07                        sta     gfx_ptr+1
6884: b9 ab 68     :LineLoop       lda     :city_bk_data,y   ;get offset of start position
6887: 85 06                        sta     gfx_ptr           ;use as pointer low byte
6889: c8                           iny                       ;advance to next byte of data
688a: a9 20        :PixelLoop      lda     #%00100000        ;color = 1
688c: 81 06                        sta     (gfx_ptr,x)       ;set pixel
688e: a5 06                        lda     gfx_ptr           ;get pointer low byte (== line offset)
6890: d9 ab 68                     cmp     :city_bk_data,y   ;have we reached the end point?
6893: e6 06                        inc     gfx_ptr           ;advance pointer
6895: 90 f3                        bcc     :PixelLoop        ;not at end, loop
                   ; 
6897: c8                           iny                       ;advance to next byte of data
6898: b9 ab 68                     lda     :city_bk_data,y   ;get next start position
689b: c9 ff                        cmp     #$ff              ;is it the end-of-line marker?
689d: d0 09                        bne     :B_LineLoop       ;no, loop (could BNE $6884 instead)
689f: c8                           iny                       ;advance to next byte of data
68a0: c0 74                        cpy     #$74              ;are we near the end of the list?
68a2: 90 02                        bcc     :NotNearEnd       ;no, branch
68a4: a0 73                        ldy     #$73              ;repeat last item in list [should be end - start]
68a6: e6 07        :NotNearEnd     inc     gfx_ptr+1         ;advance high byte of pointer to next line;
68a8: d0 da        :B_LineLoop     bne     :LineLoop         ; if we didn't roll over to $0000, loop
68aa: 60                           rts

                   ; 
                   ; Data for the city backdrop.  Each screen line is represented with pairs of
                   ; bytes, specifying inclusive start/end line offsets.  The end of the line is
                   ; marked with $ff.
                   ; 
                   ; The last entry in the table is repeated until we've drawn all lines to the
                   ; bottom of the screen.
                   ; 
68ab: 0c 0d 1d 1e+ :city_bk_data   .bulk   $0c,$0d,$1d,$1e,$75,$76,$81,$82,$e9,$ea,$fa,$fb,$ff ;line 25
68b8: 0b 0e 1c 1f+                 .bulk   $0b,$0e,$1c,$1f,$75,$77,$80,$83,$e7,$ea,$f9,$fc,$ff ;line 24
68c5: 0a 20 74 84+                 .bulk   $0a,$20,$74,$84,$e6,$fd,$ff
68cc: 09 21 73 84+                 .bulk   $09,$21,$73,$84,$e6,$fe,$ff
68d3: 08 22 73 85+                 .bulk   $08,$22,$73,$85,$e5,$fe,$ff
68da: 06 23 72 85+                 .bulk   $06,$23,$72,$85,$e4,$fe,$ff
68e1: 05 23 71 86+                 .bulk   $05,$23,$71,$86,$ac,$bb,$e3,$fe,$ff
68ea: 04 24 38 39+                 .bulk   $04,$24,$38,$39,$70,$87,$ab,$bc,$e2,$fe,$ff
68f5: 03 25 37 3a+                 .bulk   $03,$25,$37,$3a,$6f,$87,$aa,$bd,$e1,$ff,$ff
6900: 01 26 32 3b+                 .bulk   $01,$26,$32,$3b,$6b,$98,$a8,$bd,$df,$ff,$ff
690b: 00 3c 49 58+                 .bulk   $00,$3c,$49,$58,$67,$c0,$de,$fe,$ff
6914: 00 5a 65 ff+                 .bulk   $00,$5a,$65,$ff,$ff
6919: 00 5c 64 ff+                 .bulk   $00,$5c,$64,$ff,$ff ;line 13
691e: 00 ff ff                     .bulk   $00,$ff,$ff       ;line 12 (offset +$73)

                   ; 
                   ; Sets the color palette for the current wave.
                   ; 
6921: a5 a7        SetColorPalette lda     wave_num          ;get wave number; starts at 1
6923: 38                           sec
6924: e9 01                        sbc     #$01              ;make it 0-based
6926: 4a                           lsr     A                 ;divide by 2 so colors change every 2 waves
6927: c9 0a        :DivLoop        cmp     #10               ;is it < 10?
6929: 90 03                        bcc     :Sub10            ;yes, branch
692b: 38                           sec                       ;no, subtract 10
692c: e9 0a                        sbc     #10               ;do this repeatedly to divide by 10
692e: c9 0a        :Sub10          cmp     #10               ;done yet?
6930: b0 f5                        bcs     :DivLoop          ;no, keep going
                   ; 
6932: aa                           tax                       ;use as index
6933: bc 4e 69                     ldy     :color_table,x    ;get table offset
6936: a2 06                        ldx     #$06              ;start with entries 6/7
6938: b9 4e 69     :Loop           lda     :color_table,y    ;get entry
693b: 29 0f                        and     #$0f              ;grab the low nibble
693d: 95 e5                        sta     color_palette+1,x ;store that in the odd entry
693f: b9 4e 69                     lda     :color_table,y    ;get entry again
6942: 4a                           lsr     A                 ;shift the high nibble down
6943: 4a                           lsr     A
6944: 4a                           lsr     A
6945: 4a                           lsr     A
6946: 95 e4                        sta     color_palette,x   ;store that in the even entry
6948: 88                           dey                       ;move to previous table entry
6949: ca                           dex                       ;move to previous palette entry
694a: ca                           dex
694b: 10 eb                        bpl     :Loop             ;branch if not done
694d: 60                           rts

                   ; 
                   ; Color sets.  The values here are used to configure the 8-entry color palette. 
                   ; There are 10 different sets.
                   ; 
                   ; The first part is a table of offsets into the color set table.  The offset
                   ; identifies the LAST of the four bytes; for example, an offset of $0d (13)
                   ; grabs the bytes at +13, +12, +11, and +10.  The offset is calculated from the
                   ; start of this 10-byte table.
                   ; 
694e: 19 1d 21 15+ :color_table    .bulk   $19,$1d,$21,$15,$25,$2d,$31,$0d,$11,$29
                   ; 
                   ; Color table entries, packed two per byte.  The hardware takes colors as
                   ; %xxxxRGBx, so only those bits matter.  The even entries apply to the whole
                   ; screen, the odd entries can only appear in the 3bpp areas when the 3rd bit is
                   ; set.
                   ; 
                   ; Note that entries 4/5 don't usually matter, because those are marked for
                   ; color-cycling, except to the extent that they're out of phase with each other.
                   ; 
                   ; For example, wave #1 uses table #0, which is given offset $19 in the table
                   ; above.  That means the entries are at offsets $16-19.  When unpacked, the
                   ; palette entries will be:
                   ;   0e 02 06 08 06 0e 0c 0c
                   ; 
6958: 2a e0 e2 66                  .bulk   $2a,$e0,$e2,$66   ;$0a-0d (#7)
695c: 06 42 40 aa                  .bulk   $06,$42,$40,$aa   ;$0e-11 (#8)
6960: e6 22 0a cc                  .bulk   $e6,$22,$0a,$cc   ;$12-15 (#3)
6964: e2 68 6e cc                  .bulk   $e2,$68,$6e,$cc   ;$16-19 (#0)
6968: e2 a8 ae cc                  .bulk   $e2,$a8,$ae,$cc   ;$1a-1d (#1)
696c: ec 62 64 aa                  .bulk   $ec,$62,$64,$aa   ;$1e-21 (#2)
6970: c2 64 6c ee                  .bulk   $c2,$64,$6c,$ee   ;$22-25 (#4)
6974: 62 ea e6 cc                  .bulk   $62,$ea,$e6,$cc   ;$26-29 (#9)
6978: 82 6e 68 cc                  .bulk   $82,$6e,$68,$cc   ;$2a-2d (#5)
697c: 4a ee 48 22                  .bulk   $4a,$ee,$48,$22   ;$2e-31 (#6)

                   ; 
                   ; Zeroes out $0600-$2eff, which clears lines 230 through 68 on the screen, as
                   ; well as the small RAM area at $0600-063f.  This is used when entering initials
                   ; for high scores, to clear most of the screen without erasing the city area.
                   ; 
                   ClearScreenPartial
6980: a9 06                        lda     #$06              ;start at $0600
6982: a2 28                        ldx     #$28              ;clear $29 256-byte pages
6984: d0 04                        bne     :DoZero           ;(always)

                   ; 
                   ; Zeroes out $0200-3fff, clearing the screen to color 0.  Also erases the data
                   ; area at $600-63f.
                   ; 
6986: a2 3d        ClearScreen     ldx     #$3d              ;clear $3e 256-byte pages
6988: a9 02                        lda     #$02              ;start at $0200
                   ; 
                   ]mem_ptr        .var    $06    {addr/2}

698a: 85 07        :DoZero         sta     ]mem_ptr+1        ;set high byte of pointer
698c: a0 00                        ldy     #$00
698e: 84 06                        sty     ]mem_ptr          ;set low byte of pointer to zero
                   ; 
6990: a9 00                        lda     #$00              ;color = 0
6992: 91 06        :Loop           sta     (]mem_ptr),y
6994: c8                           iny
6995: d0 fb                        bne     :Loop             ;loop if not done with page
6997: e6 07                        inc     ]mem_ptr+1        ;advance pointer
6999: ca                           dex                       ;decrement count; done yet?
699a: 10 f6                        bpl     :Loop             ;no, branch
699c: 60                           rts

                   ; 
                   ; Gets a pointer to a character glyph.
                   ; 
                   ; On entry:
                   ;   A-reg: ASCII value of character to find
                   ; 
                   ; On exit:
                   ;   $0d-0e: pointer to glyph
                   ; 
                   ]glyph_ptr      .var    $0d    {addr/2}

699d: 29 7f        GetGlyphPtr     and     #$7f              ;strip high bit
699f: c9 41                        cmp     #‘A’              ;lower than 'A'?
69a1: 90 07                        bcc     :Special          ;yes, do special chars
69a3: 29 3f                        and     #%00111111        ;convert $40-5f to $00-1f
69a5: a2 02                        ldx     #$02              ;table 2
69a7: b8                           clv
69a8: 50 18                        bvc     :DoPrint          ;(always)

69aa: c9 3a        :Special        cmp     #‘:’              ;lower than ':'?
69ac: 90 06                        bcc     :CheckNums        ;yes, see if it's a number
69ae: 20 d3 69                     jsr     MapSpecialChar    ;no, try to map to a glyph
69b1: b8                           clv
69b2: 50 0e                        bvc     :DoPrint          ;(always)

69b4: c9 30        :CheckNums      cmp     #‘0’              ;greater than '0'?
69b6: b0 06                        bcs     :IsNumber         ;yes, it's a number
69b8: 20 d3 69                     jsr     MapSpecialChar    ;no, try to map to a glyph
69bb: b8                           clv
69bc: 50 04                        bvc     :DoPrint          ;(always)

69be: 29 0f        :IsNumber       and     #%00001111        ;convert $30-39 to $00-09
69c0: a2 00                        ldx     #$00              ;table 0
69c2: 0a           :DoPrint        asl     A                 ;multiply glyph index by 8
69c3: 0a                           asl     A
69c4: 0a                           asl     A
69c5: 18                           clc
69c6: 7d 1e 6a                     adc     glyph_base_tbl,x  ;add table base
69c9: 85 0d                        sta     ]glyph_ptr
69cb: bd 1f 6a                     lda     glyph_base_tbl+1,x
69ce: 69 00                        adc     #$00
69d0: 85 0e                        sta     ]glyph_ptr+1
69d2: 60                           rts

                   ; 
                   ; Map special chars [ @:?#$%] to glyph indices.
                   ; 
                   ; On entry:
                   ;   A-reg: char ASCII value
                   ; 
                   ; On exit:
                   ;   A-reg: glyph, or $00 if no match found
                   ;   X-reg: table number: $06 if match found, $04 if not
                   ; 
69d3: a2 06        MapSpecialChar  ldx     #$06              ;start on last element
69d5: dd e8 69     :Loop           cmp     char_map_char_tbl,x ;compare to requested char
69d8: d0 06                        bne     :NoMatch          ;didn't match, branch
69da: bd ef 69                     lda     char_map_glyph_tbl,x ;get index of glyph
69dd: a2 06                        ldx     #$06              ; in table 6
69df: 60                           rts

69e0: ca           :NoMatch        dex                       ;move to next entry
69e1: 10 f2                        bpl     :Loop             ;loop if not done
69e3: a9 00                        lda     #$00              ;space char
69e5: a2 04                        ldx     #$04              ; in table 4
69e7: 60                           rts

                   ; 
                   ; Map character values that don't have ASCII counterparts, or don't align with
                   ; the ASCII table, to their corresponding glyph numbers.
                   ; 
                   char_map_char_tbl
69e8: 20                           .dd1    ‘ ’               ;space is ASCII, but at index $00
69e9: 40                           .dd1    ‘@’               ;(C) copyright symbol
69ea: 3a                           .dd1    ‘:’               ;colon is also ASCII, but at index $09
69eb: 3f                           .dd1    ‘?’               ;(P) sound copyright symbol
69ec: 23                           .dd1    ‘#’               ;A + umlaut
69ed: 24                           .dd1    ‘$’               ;O + umlaut
69ee: 25                           .dd1    ‘%’               ;U + umlaut
                   char_map_glyph_tbl
69ef: 00 08 09 0a+                 .bulk   $00,$08,$09,$0a,$0b,$0c,$0d

                   ; 
                   ; Prints a string at the specified X/Y position.
                   ; 
                   ; On entry:
                   ;   $08: preserve background flag
                   ;   $0b: foreground color index (%PPP00000)
                   ;   $0c: background color index (%PPP00000)
                   ;   $1d-1e: string pointer
                   ;   $1f: initial horizontal position
                   ;   $20: initial vertical position
                   ;   $21: vertical glyph size multiplier
                   ;   $22: horizontal glyph size multiplier
                   ; 
                   ]xpos           .var    $00    {addr/1}
                   ]ypos           .var    $01    {addr/1}
                   ]char_tmp       .var    $05    {addr/1}
                   ]str_ptr        .var    $1d    {addr/2}
                   ]horiz_posn     .var    $1f    {addr/1}
                   ]vert_posn      .var    $20    {addr/1}
                   ]vert_mult      .var    $21    {addr/1}
                   ]horiz_mult     .var    $22    {addr/1}

69f6: a0 00        PrintString     ldy     #$00
69f8: b1 1d        :Loop           lda     (]str_ptr),y      ;get char
69fa: 85 05                        sta     ]char_tmp         ;save for later
69fc: 20 9d 69                     jsr     GetGlyphPtr       ;get pointer to glyph in $0d-0e
69ff: a5 1f                        lda     ]horiz_posn       ;copy coordinates
6a01: 85 00                        sta     ]xpos
6a03: a5 20                        lda     ]vert_posn
6a05: 85 01                        sta     ]ypos
6a07: 98                           tya                       ;preserve Y-reg on stack
6a08: 48                           pha
6a09: 20 2d 66                     jsr     DrawGlyphPtr      ;draw the glyph
                   ; 
6a0c: a5 22                        lda     ]horiz_mult       ;get the horizontal multiplier
6a0e: 0a                           asl     A                 ;multiply by 8 (the width of a character glyph)
6a0f: 0a                           asl     A
6a10: 0a                           asl     A
6a11: 18                           clc
6a12: 65 1f                        adc     ]horiz_posn       ;update horizontal position for next char
6a14: 85 1f                        sta     ]horiz_posn
6a16: 68                           pla                       ;restore Y-reg
6a17: a8                           tay
6a18: c8                           iny                       ;advance to next character
6a19: a5 05                        lda     ]char_tmp         ;was previous char the end of the DCI string?
6a1b: 10 db                        bpl     :Loop             ;no, loop
6a1d: 60                           rts

                   ; 
                   ; Pointers to glyph bitmaps for general character output routine.
                   ; 
6a1e: 1a 73        glyph_base_tbl  .dd2    glyphs_0          ;$00 '0' - '9'
6a20: 62 73                        .dd2    glyphs_2          ;$02 'A' - 'Z'
                   ; This points to a blank space glyph, followed by '0'-'9'.
6a22: 12 73        glyph_digit_tbl .dd2    glyphs_4          ;$04 (unknown char or suppressed leading '0')
6a24: 3a 74                        .dd2    glyphs_6          ;$06 '@', ':', '?', '$', '%', cities, etc.

                   ; 
                   ; Gets the pointers and horizontal position for message N.
                   ; 
                   ; On entry:
                   ;   Y-reg: message index * 3
                   ; 
                   ; On exit:
                   ;   $1f: horizontal position
                   ;   $1d-1e: pointer to string
                   ; 
                   ]str_ptr        .var    $1d    {addr/2}
                   ]horz_posn      .var    $1f    {addr/1}

6a26: b1 2a        GetMessageN     lda     (msg_base_ptr),y  ;get horizontal position
6a28: c0 54                        cpy     #84               ;message number > 28? (28*3=84)
6a2a: 90 10                        bcc     :LowMsg           ;no, show language-specific version
6a2c: b9 12 6e                     lda     msgs_en,y         ;yes, pull from tail end of English table
6a2f: 85 1f                        sta     ]horz_posn
6a31: b9 13 6e                     lda     msgs_en+1,y
6a34: 85 1d                        sta     ]str_ptr
6a36: b9 14 6e                     lda     msgs_en+2,y
6a39: b8                           clv
6a3a: 50 0a                        bvc     :StaRet           ;(always)

6a3c: 85 1f        :LowMsg         sta     ]horz_posn
6a3e: c8                           iny                       ;get the string pointer
6a3f: b1 2a                        lda     (msg_base_ptr),y
6a41: 85 1d                        sta     ]str_ptr
6a43: c8                           iny
6a44: b1 2a                        lda     (msg_base_ptr),y
6a46: 85 1e        :StaRet         sta     ]str_ptr+1
6a48: 60                           rts

                   ; 
                   ; Entry points for message draw/erase.
                   ; 
                   ; Erasing a specific message is useful for things like the "LOW" and "OUT"
                   ; indicators.  Erasing the entire line is handy for double-width messages and
                   ; variable-width messages.
                   ; 
                   ; (See DoPrintMessage for entry/exit conditions.)
                   ; 
                   • Clear variables
                   ]index_tmp      .var    $1f    {addr/1}
                   ]msg_vert_posn  .var    $20    {addr/1}
                   ]vert_mult      .var    $21    {addr/1}
                   ]horz_mult      .var    $22    {addr/1}

                   EraseMessageLine
6a49: a0 a0                        ldy     #$a0
6a4b: 30 0a                        bmi     DoPrintMessage    ;(always)

6a4d: a0 80        EraseMessage    ldy     #$80
6a4f: 30 06                        bmi     DoPrintMessage    ;(always)

6a51: a0 00        PrintMessage    ldy     #$00
6a53: 10 02                        bpl     DoPrintMessage    ;(always)

6a55: a0 ff        PrintMessagePr  ldy     #$ff
                   ; 
                   ; Prints a message.
                   ; 
                   ; On entry:
                   ;   Y-reg: action:
                   ;     $00: draw message, don't preserve background
                   ;     $ff: draw message, do preserve background
                   ;     $80: erase message
                   ;     $a0: erase entire line message is on
                   ;   A-reg: message index
                   ; 
6a57: 84 08        DoPrintMessage  sty     preserve_bg_flag  ;store flag and "command" here
6a59: a8                           tay
6a5a: b9 c5 6c                     lda     msg_glyph_size_tbl,y ;get value
6a5d: 29 0f                        and     #%00001111        ;mask off the Y multiplier
6a5f: 85 21                        sta     ]vert_mult        ;save to ZP
6a61: b9 c5 6c                     lda     msg_glyph_size_tbl,y ;get same value
6a64: 4a                           lsr     A                 ;shift X multiplier into low nibble
6a65: 4a                           lsr     A
6a66: 4a                           lsr     A
6a67: 4a                           lsr     A
6a68: 85 22                        sta     ]horz_mult        ;save to ZP
6a6a: b9 ed 6c                     lda     msg_vertical_tbl,y ;get vertical position
6a6d: 85 20                        sta     ]msg_vert_posn
                   ; 
6a6f: 98                           tya                       ;put message index arg back in A-reg
6a70: 85 1f                        sta     ]index_tmp        ;save to ZP so we can multiply by 3
6a72: 0a                           asl     A
6a73: 18                           clc
6a74: 65 1f                        adc     ]index_tmp
6a76: a8                           tay
6a77: 20 26 6a                     jsr     GetMessageN       ;get pointer in $1d-1e, horiz posn in $1f
                   ; 
6a7a: a9 e0                        lda     #%11100000        ;fg color = 7
6a7c: 85 0b                        sta     draw_fg_color
6a7e: a9 00                        lda     #$00              ;bg color = 0
6a80: 85 0c                        sta     draw_bg_color
                   ; Call an appropriate function.  At this point we have:
                   ;   $1d-1e: pointer
                   ;   $1f: horizontal start position
                   ;   $20: vertical start position
                   ;   $21: horizontal size multiplier
                   ;   $22: vertical size multiplier
6a82: a5 08                        lda     preserve_bg_flag  ;check command
6a84: c9 80                        cmp     #$80              ;string erase?
6a86: d0 03                        bne     :Not80            ;no, branch
6a88: 4c 38 6b                     jmp     EraseStringPartial ;$80: erase the string

6a8b: c9 a0        :Not80          cmp     #$a0              ;line erase?
6a8d: d0 03                        bne     :NotA0            ;no, branch
6a8f: 4c 30 6b                     jmp     EraseStringLine   ;$a0: erase entire line string is on

6a92: 4c f6 69     :NotA0          jmp     PrintString       ;$00/ff: print string

                   ; 
                   ; Table of addresses of pointers to strings.  One entry here for each language.
                   ; 
6a95: 12 6e        msg_lang_tbl    .dd2    msgs_en           ;English
6a97: 16 6d                        .dd2    msgs_fr           ;French
6a99: 6a 6d                        .dd2    msgs_de           ;German
6a9b: be 6d                        .dd2    msgs_es           ;Spanish

                   ; 
                   ; Draws a number between 0 and 18.  Values in the range [0,9] are drawn as a
                   ; single digit (i.e no leading '0' or space for one).
                   ; 
                   ; The background color ($0c) is set to 0, and the preserve-background flag ($08)
                   ; is disabled.
                   ; 
                   ; On entry:
                   ;   A-reg: 0-9, or $ff to draw a blank space (for leading 0 suppression)
                   ;   X-reg: glyph center horizontal position
                   ;   Y-reg: glyph center vertical position
                   ;   $0b: foreground color index (%PPP00000)
                   ; 
                   ; On exit:
                   ;   (X/Y-reg preserved)
                   ; 
                   ]xc             .var    $00    {addr/1}
                   ]yc             .var    $01    {addr/1}
                   ]tmp_a          .var    $02    {addr/1}
                   ]ptr            .var    $0d    {addr/2}
                   ]msg_horiz_posn .var    $1f    {addr/1}
                   ]saved_x        .var    $23    {addr/1}
                   ]saved_y        .var    $24    {addr/1}

6a9d: 86 1f        DrawSmallNum    stx     ]msg_horiz_posn   ;save vertical / horizontal position
6a9f: 84 20                        sty     ]msg_vert_posn
6aa1: 86 23        DoDrawSmallNum  stx     ]saved_x          ;preserve X/Y-reg
6aa3: 84 24                        sty     ]saved_y
6aa5: 85 02                        sta     ]tmp_a            ;save number
6aa7: c9 13                        cmp     #19               ;is value >= 19?
6aa9: b0 13                        bcs     :DrawBlank        ;yes, print a blank space instead of a number
6aab: c9 0a                        cmp     #10               ;is it < 10?
6aad: 90 0a                        bcc     :DoSingle         ;yes, draw single digit
6aaf: 38                           sec
6ab0: e9 0a                        sbc     #10               ;subtract 10 to get 0-9
6ab2: 85 02                        sta     ]tmp_a            ;update number
6ab4: a9 01                        lda     #$01
6ab6: 20 c8 6a                     jsr     :DoDraw           ;draw a '1'
                   ; 
6ab9: a5 02        :DoSingle       lda     ]tmp_a            ;get value 0-9
6abb: b8                           clv
6abc: 50 02                        bvc     :CallDoDraw       ;(always)

6abe: a9 ff        :DrawBlank      lda     #$ff              ;draw a blank space instead of a number
                   ; 
6ac0: 20 c8 6a     :CallDoDraw     jsr     :DoDraw           ;draw the digit
6ac3: a6 23                        ldx     ]saved_x          ;restore X/Y-reg
6ac5: a4 24                        ldy     ]saved_y
6ac7: 60                           rts

                   ; Draw the digit in the accumulator (0-9 or $ff).
6ac8: 18           :DoDraw         clc
6ac9: 69 01                        adc     #$01              ;convert $ff/0-9 to 0-10
6acb: 0a                           asl     A                 ;each glyph is 8 bytes
6acc: 0a                           asl     A
6acd: 0a                           asl     A
6ace: 18                           clc
6acf: 6d 22 6a                     adc     glyph_digit_tbl   ;add pointer to space/digits from table
6ad2: 85 0d                        sta     ]ptr
6ad4: ad 23 6a                     lda     glyph_digit_tbl+1
6ad7: 69 00                        adc     #$00
6ad9: 85 0e                        sta     ]ptr+1
6adb: a5 1f                        lda     ]msg_horiz_posn   ;set horizontal / vertical position
6add: 85 00                        sta     ]xc
6adf: a5 20                        lda     ]msg_vert_posn
6ae1: 85 01                        sta     ]yc
6ae3: a9 00                        lda     #%00000000        ;bg color = 0
6ae5: 85 0c                        sta     draw_bg_color
6ae7: a9 00                        lda     #$00
6ae9: 85 08                        sta     preserve_bg_flag  ;overwrite background
6aeb: 20 27 66                     jsr     DrawGlyphPtrNoSc  ;draw the glyph, no scaling
6aee: a5 1f                        lda     ]msg_horiz_posn   ;advance the horizontal position
6af0: 18                           clc
6af1: 69 08                        adc     #$08              ;each glyph is 8 pixels wide (we use scale=1)
6af3: 85 1f                        sta     ]msg_horiz_posn   ;only really matters for value >= 10 case
6af5: 60                           rts

                   ; 
                   ; Prints a 3-byte BCD number (e.g. score).
                   ; 
                   ; On entry:
                   ;   A-reg: foreground color (%PPP00000)
                   ;   X-reg: horizontal position
                   ;   Y-reg: vertical position
                   ;   $d1-d3: BCD value
                   ; 
                   ]print_zero_flag .var   $05    {addr/1}

6af6: 86 1f        PrintBcdNumber  stx     ]msg_horiz_posn   ;set position
6af8: 84 20                        sty     ]msg_vert_posn
6afa: 85 0b                        sta     draw_fg_color     ;set color
6afc: a2 02                        ldx     #$02              ;print 3 bytes (0-2)
6afe: a0 00                        ldy     #$00
6b00: 84 05                        sty     ]print_zero_flag  ;clear flag so we don't draw '0'
                   ; 
6b02: b5 d1        :Loop           lda     bcd3_acc,x        ;get byte
6b04: 4a                           lsr     A                 ;shift high nibble to low
6b05: 4a                           lsr     A
6b06: 4a                           lsr     A
6b07: 4a                           lsr     A
6b08: 20 1e 6b                     jsr     :PrintDigit       ;print it
6b0b: b5 d1                        lda     bcd3_acc,x        ;get same byte
6b0d: e0 00                        cpx     #$00              ;are we on the rightmost byte?
6b0f: d0 04                        bne     :NotRight         ;no, branch
6b11: a0 ff                        ldy     #$ff              ;yes, set the "do print '0'" flag
6b13: 84 05                        sty     ]print_zero_flag
6b15: 29 0f        :NotRight       and     #$0f              ;strip excess bits
6b17: 20 1e 6b                     jsr     :PrintDigit       ;print it
6b1a: ca                           dex                       ;done yet?
6b1b: 10 e5                        bpl     :Loop             ;no, loop
6b1d: 60                           rts

6b1e: f0 05        :PrintDigit     beq     :PrintZero        ;(flags set for A-reg) if zero, branch
6b20: 85 05                        sta     ]print_zero_flag  ;nonzero, set the "do print '0'" flag
6b22: b8                           clv
6b23: 50 08                        bvc     :JmpDraw          ;(always)

6b25: a9 00        :PrintZero      lda     #$00              ;(should already be $00?)
6b27: a4 05                        ldy     ]print_zero_flag  ;check "do print '0'" flag
6b29: d0 02                        bne     :JmpDraw          ;it's set, branch
6b2b: a9 ff                        lda     #$ff              ;not set, replace $00 with $ff
6b2d: 4c a1 6a     :JmpDraw        jmp     DoDrawSmallNum    ;draw the glyph

                   ; 
                   ; Erases the entire screen line that a string is on.  The caller identified a
                   ; specific string to erase, but the only thing we care about is its vertical
                   ; position.
                   ; 
                   ; Only affects the 2bpp pixel area.
                   ; 
                   • Clear variables
                   ]string_ptr     .var    $1d    {addr/2}
                   ]horz_posn      .var    $1f    {addr/1}
                   ]vert_posn      .var    $20    {addr/1}
                   ]counter        .var    $98    {addr/1}
                   ]end_offset     .var    $99    {addr/1}

6b30: a9 04        EraseStringLine lda     #$04              ;start at horizontal offset 4, because we subtract 4
6b32: 85 1f                        sta     ]horz_posn        ; later on
6b34: a0 20                        ldy     #32               ;screen is 256 wide, chars are 8x8, max 32 chars
6b36: d0 08                        bne     :EraseScreenChunk ;(always)

                   ; 
                   ; Erases a string, overwriting it with the background color.
                   ; 
                   ; On entry:
                   ;   $1d: string pointer
                   ; 
                   EraseStringPartial
6b38: a0 ff                        ldy     #$ff              ;start at -1, so first loop increments to zero
6b3a: c8           :Loop           iny
6b3b: b1 1d                        lda     (]string_ptr),y
6b3d: 10 fb                        bpl     :Loop             ;loop until we find end of DCI string
6b3f: c8                           iny                       ;increment Y-reg so it's equal to string length
                   ; 
                   ; Erase a chunk of the screen.  At this point:
                   ;   Y-reg: string length (8 pixels per char)
                   ;   $1f: horizontal start position
                   ;   $20: vertical start position
                   ; 
                   ; We set the 2bpp pixel values to zero.  The 3rd pixel bit is not affected.
                   ; 
                   :EraseScreenChunk
6b40: 84 99                        sty     ]end_offset       ;store the string len
6b42: 06 99                        asl     ]end_offset       ;double it (8x8 per char, 4 pixels per byte)
6b44: c6 99                        dec     ]end_offset       ;subtract 1 so we can use it as an offset
6b46: a5 1f                        lda     ]horz_posn        ;get horizontal start position
6b48: 38                           sec
6b49: e9 04                        sbc     #$04              ;subtract 4 to match string print routine
6b4b: 24 fc                        bit     cocktail_flip     ;is screen flipped vertically?
6b4d: 50 02                        bvc     :NoFlip           ;no, branch
6b4f: 49 ff                        eor     #$ff              ;yes, flip horizontally to complete rotation
6b51: 85 1f        :NoFlip         sta     ]horz_posn        ;save for use as low byte of pointer
                   ; Form a pointer into the 2bpp region.  We can do this quickly because the
                   ; region ends at $4000.  If we take the line number [0,230] and invert the bits
                   ; with EOR #$ff, we get [$ff,$19], which works as the high byte of a MADSEL-mode
                   ; pointer.  For regular RAM we have 64 bytes per line rather than 256, so we
                   ; just divide by 4, and get [$3f,$06].
                   ; 
                   ; Example: message table puts string on line 3.  $03 ^ $ff = $fc, -4 = $f8, /4 =
                   ; $3e.  $3e00 is the start of line 223, which is (230-3)-4.
6b53: a5 20                        lda     ]vert_posn        ;get vertical center position
6b55: 49 ff                        eor     #$ff              ;invert it as part of forming pointer
6b57: 38                           sec
6b58: e9 04                        sbc     #$04              ;subtract 4 from inv. val to match string routine
6b5a: 85 20                        sta     ]vert_posn        ;save for use as high byte of pointer
6b5c: a9 07                        lda     #$07              ;init counter to erase 8 lines
6b5e: 85 98                        sta     ]counter
6b60: 46 20                        lsr     ]vert_posn        ;divide by 4
6b62: 66 1f                        ror     ]horz_posn        ;consider line 3... we eor #$ff to get $fc, then
6b64: 46 20                        lsr     ]vert_posn        ; subtract 4 to get $f8; divide by 4 to get $3e00
6b66: 66 1f                        ror     ]horz_posn
6b68: 24 fc                        bit     cocktail_flip     ;is screen flipped?
6b6a: 50 07                        bvc     :LineLoop         ;no, branch
6b6c: a5 1f                        lda     ]horz_posn        ;yes, flip start position
6b6e: 38                           sec
6b6f: e5 99                        sbc     ]end_offset
6b71: 85 1f                        sta     ]horz_posn
                   ; 
                   ]ptr            .var    $1f    {addr/2}   ;horz/vert ZP now holds pointer

6b73: a4 99        :LineLoop       ldy     ]end_offset       ;get offset of byte at end of range
6b75: a9 00        :EraseLoop      lda     #$00
6b77: 91 1f                        sta     (]ptr),y          ;zero out 4 pixels
6b79: 88                           dey
6b7a: 10 f9                        bpl     :EraseLoop
                   ; 
6b7c: a5 1f                        lda     ]ptr              ;update pointer
6b7e: 18                           clc
6b7f: 69 40                        adc     #64               ;64 bytes per line
6b81: 85 1f                        sta     ]ptr
6b83: 90 02                        bcc     :NoInc            ;branch if we didn't overflow low byte
6b85: e6 20                        inc     ]ptr+1            ;increment the high byte
6b87: c6 98        :NoInc          dec     ]counter          ;done yet?
6b89: 10 e8                        bpl     :LineLoop         ;no, branch
6b8b: 60                           rts

                   ; 
                   ; Updates scrolling text.  This outputs one character if the time is right, and
                   ; resets the entire thing if the number of purchased credits changes.
                   ; 
                   • Clear variables
                   ]thing_ptr?     .var    $06    {addr/2}
                   ]bcd_credits    .var    $98    {addr/1}

                   UpdateScrollText
6b8c: a9 00                        lda     #$00
6b8e: 85 b9                        sta     cur_plyr_num      ;set to player 1
6b90: a5 fc                        lda     cocktail_flip     ;rotate screen toward player 1
6b92: 29 80                        and     #%10000000        ;clear flip bits (but keep cocktail-mode bit)
6b94: 85 fc                        sta     cocktail_flip     ; so we only have to scroll right-to-left
                   ; 
6b96: a5 66                        lda     credit_count      ;get number of credits purchased
6b98: c9 28                        cmp     #40               ;more than 40?
6b9a: 90 04                        bcc     :Lt40Cred         ;no, branch
6b9c: a9 28                        lda     #40               ;cap at 40
6b9e: 85 66                        sta     credit_count      ;store it, discarding excess credits
                   ; Convert credit count to BCD equivalent.
6ba0: f8           :Lt40Cred       sed                       ;enable BCD mode
6ba1: 85 99                        sta     tmp_99            ;store credit count in ZP
6ba3: a9 00                        lda     #$00
6ba5: 85 98                        sta     ]bcd_credits      ;init BCD form
6ba7: a0 07                        ldy     #$07              ;8 bits per byte
6ba9: 06 99        :ConvLoop       asl     tmp_99            ;shift high bit into carry
6bab: a5 98                        lda     ]bcd_credits      ;get current value
6bad: 65 98                        adc     ]bcd_credits      ;double credits in BCD mode, adding 1 if carry set
6baf: 85 98                        sta     ]bcd_credits      ;write it back
6bb1: 88                           dey                       ;done all 8 bits?
6bb2: 10 f5                        bpl     :ConvLoop         ;no, branch
6bb4: d8                           cld                       ;disable BCD mode
                   ; 
6bb5: a5 98                        lda     ]bcd_credits      ;get BCD credit count
6bb7: c5 29                        cmp     txsc_credits      ;has number of credits changed since last time?
6bb9: f0 22                        beq     :NoCreditChange   ;no, branch
6bbb: 85 29                        sta     txsc_credits      ;store in $29 for special message
6bbd: a5 66                        lda     credit_count      ;is credit count zero?
6bbf: f0 05                        beq     :ZeroCred         ;yes, branch
6bc1: a2 00                        ldx     #$00              ;start in "press start" series
6bc3: b8                           clv
6bc4: 50 0c                        bvc     :SetSeries

6bc6: a5 f3        :ZeroCred       lda     r10_irq_mod       ;get R10 switches
6bc8: 29 03                        and     #%00000011        ;mask to get coins-per-play setting
6bca: aa                           tax
6bcb: bd 2f 6c                     lda     play_cost_msg_tbl,x ;get message index
6bce: 85 28                        sta     txsc_play_cost    ;store in $28 for special message
6bd0: a2 05                        ldx     #$05              ;start in "insert coins" series
6bd2: 86 25        :SetSeries      stx     txsc_out_idx
                   ; We clear the message and start over when the number of credits changes.
6bd4: 20 b6 6c                     jsr     SetBottomBit3     ;set bit 3 for all of bottom text area
6bd7: a9 00                        lda     #$00              ; (clearing it to the city background color)
6bd9: 85 27                        sta     txsc_char_delay   ;zero delay so we start drawing immediately
6bdb: 85 26                        sta     txsc_msg_offset   ;draw from the start of the string
                   ; 
6bdd: a5 27        :NoCreditChange lda     txsc_char_delay   ;time to draw next char?
6bdf: f0 05                        beq     :DrawNext         ;yes, branch
6be1: c6 27                        dec     txsc_char_delay   ;no, update countdown
6be3: b8                           clv                       ; and bail
6be4: 50 48                        bvc     :Return           ;(always)

6be6: a6 25        :DrawNext       ldx     txsc_out_idx      ;get text scroll item index
6be8: bd 42 6c                     lda     txsc_spec_tbl,x   ;get text scroll item
6beb: c9 00                        cmp     #$00              ;something special?
6bed: d0 09                        bne     :SpecialMsg       ;yes, branch
6bef: bd 38 6c                     lda     txsc_msg_tbl,x    ;no, just get message index
6bf2: 20 7f 6c                     jsr     TxscDrawChar      ;draw the next character
6bf5: b8                           clv
6bf6: 50 1b                        bvc     :CharDrawn        ;(always)

6bf8: bd 38 6c     :SpecialMsg     lda     txsc_msg_tbl,x    ;get ZP address from table
6bfb: 85 06                        sta     ]thing_ptr?       ;use as low byte of pointer
6bfd: a0 00                        ldy     #$00
6bff: 84 07                        sty     ]thing_ptr?+1     ;set high byte of pointer to zero
6c01: b1 06                        lda     (]thing_ptr?),y   ;get value from ZP
6c03: bc 42 6c                     ldy     txsc_spec_tbl,x   ;get special index
6c06: c0 02                        cpy     #$02              ;is it special #2 (play cost)?
6c08: d0 06                        bne     :Not2             ;no, branch
6c0a: 20 7f 6c                     jsr     TxscDrawChar      ;draw message
6c0d: b8                           clv
6c0e: 50 03                        bvc     :CharDrawn        ;(always)

6c10: 20 56 6c     :Not2           jsr     TxscPrintBcd      ;handles special #1 (number of credits)
                   ; 
6c13: a5 27        :CharDrawn      lda     txsc_char_delay   ;check the end-of-message flag
6c15: c9 ff                        cmp     #$ff
6c17: d0 15                        bne     :Return           ;not at end yet, keep going
                   ; We reached the end of the previous message.  Pick a new one.
6c19: a6 25                        ldx     txsc_out_idx      ;get table index
6c1b: bd 4c 6c                     lda     txsc_inter_del_tbl,x ;get inter-message spacing
6c1e: 85 27                        sta     txsc_char_delay   ;set delay
6c20: e8                           inx                       ;advance to next table entry
6c21: bd 42 6c                     lda     txsc_spec_tbl,x   ;check next "special" value
6c24: c9 03                        cmp     #$03              ;is it 3 (end of list)?
6c26: d0 04                        bne     :Not3             ;no, branch
6c28: bd 38 6c                     lda     txsc_msg_tbl,x    ;special #3: get value from msg table
6c2b: aa                           tax                       ; and use that as next entry index
6c2c: 86 25        :Not3           stx     txsc_out_idx      ;update table index
6c2e: 60           :Return         rts

                   ; 
                   ; Message indices for coins-per-play settings.
                   ; 
                   play_cost_msg_tbl
6c2f: 25                           .dd1    $25               ;"FREE PLAY"
6c30: 0a                           .dd1    $0a               ;"1 COIN 2 PLAYS"
6c31: 0b                           .dd1    $0b               ;"1 COIN 1 PLAY"
6c32: 0c                           .dd1    $0c               ;"2 COINS 1 PLAY"

                   ; 
                   ; Clears the credit count maintained by the text scroll code.  This ensures that
                   ; the current count will be different, so the code will clear the message area
                   ; and restart the message stream.
                   ; 
6c33: a9 ff        TxscResetStream lda     #$ff
6c35: 85 29                        sta     txsc_credits
6c37: 60                           rts

                   ; 
                   ; Text scroll message indices.  Special entries are noted as non-zero values in
                   ; the second table.  For non-special entries, this is the index of the message
                   ; to display; for special entries it acts as a parameter.  There are two
                   ; separate series of messages here, one for zero credits, one for nonzero
                   ; credits.
                   ; 
                   ;  0: $00/$03="PRESS START"
                   ;  1: $00/$0d="CREDITS:"
                   ;  2: $01/$29=number of credits as BCD value, in ZP location $29
                   ;  3: $00/$1e="ATARI (C)(P) 1980"
                   ;  4: $03/$00=end of list, start over at #0
                   ;  5: $00/$0e="GAME OVER"
                   ;  6: $00/$04="INSERT COINS"
                   ;  7: $02/$28=coins-per-play message, in ZP location $28
                   ;  8: $00/$1e="ATARI (C)(P) 1980"
                   ;  9: $03/$05=end of list, start over at #5
                   ; 
6c38: 03 0d 29 1e+ txsc_msg_tbl    .bulk   $03,$0d,$29,$1e,$00,$0e,$04,$28,$1e,$05
6c42: 00 00 01 00+ txsc_spec_tbl   .bulk   $00,$00,$01,$00,$03,$00,$00,$02,$00,$03
                   ; 
                   ; Text scroll delay between messages.  This is the number of pixels that the
                   ; message should be scrolled before moving on to the next message.  This is done
                   ; to leave some space between them.
                   ; 
                   ; These are all 32 (4 spaces) except for the second entry, which is a single
                   ; space following "CREDITS:".
                   ; 
                   txsc_inter_del_tbl
6c4c: 20 08 20 20+                 .bulk   $20,$08,$20,$20,$20,$20,$20,$20,$20,$20

                   ; 
                   ; Draws a one- or two-digit BCD value in the text scroll area.
                   ; 
6c56: a4 26        TxscPrintBcd    ldy     txsc_msg_offset   ;get offset within message
6c58: f0 0a                        beq     :FirstChar        ;first char, branch
6c5a: a0 ff                        ldy     #$ff
6c5c: 84 27                        sty     txsc_char_delay   ;signal end of message
6c5e: c8                           iny
6c5f: 84 26                        sty     txsc_msg_offset   ;reset string offset to zero
6c61: b8                           clv
6c62: 50 0e                        bvc     :PrintNibble      ;print low nibble

6c64: a0 07        :FirstChar      ldy     #$07
6c66: 84 27                        sty     txsc_char_delay   ;set delay to 8 (pixels per glyph)
6c68: e6 26                        inc     txsc_msg_offset   ;advance to next char (actually lower nibble)
6c6a: 4a                           lsr     A                 ;shift high nibble into low
6c6b: 4a                           lsr     A
6c6c: 4a                           lsr     A
6c6d: 4a                           lsr     A
6c6e: d0 02                        bne     :PrintNibble      ;if nonzero, print it
6c70: a9 ff                        lda     #$ff              ;otherwise, skip it
6c72: 29 0f        :PrintNibble    and     #%00001111        ;strip high bits
6c74: c9 0a                        cmp     #10               ;is it >= 10?
6c76: b0 06                        bcs     :Return           ;yes, bail
6c78: 18                           clc
6c79: 69 30                        adc     #‘0’              ;convert to ASCII digit
6c7b: 20 9c 6c                     jsr     DrawTxscChar2     ;draw digit
6c7e: 60           :Return         rts

                   ; 
                   ; Draws a character for the scrolling text display.
                   ; 
                   ; On entry:
                   ;   A-reg: message index
                   ;   $26: char offset within string
                   ; 
                   ; On exit:
                   ;   $26: incremented by one, or reset to zero
                   ;   $27: $07 if more chars remain, or $ff if done
                   ; 
                   • Clear variables
                   ]xc             .var    $00    {addr/1}
                   ]yc             .var    $01    {addr/1}
                   ]tmp            .var    $1d    {addr/1}

6c7f: 85 1d        TxscDrawChar    sta     ]tmp              ;store index in ZP
6c81: 0a                           asl     A                 ;double it
6c82: 18                           clc
6c83: 65 1d                        adc     ]tmp              ;add to itself
6c85: a8                           tay                       ;put index * 3 in Y-reg
6c86: 20 26 6a                     jsr     GetMessageN       ;get pointer to string in $1d-1e
                   ]char_ptr       .var    $1d    {addr/2}

6c89: a4 26                        ldy     txsc_msg_offset   ;get current offset
6c8b: e6 26                        inc     txsc_msg_offset   ;increment for next time
6c8d: a9 07                        lda     #$07              ;scroll 8x before outputting next char
6c8f: 85 27                        sta     txsc_char_delay   ;set this to 7
6c91: b1 1d                        lda     (]char_ptr),y     ;get next character
6c93: 10 07                        bpl     DrawTxscChar2     ;branch if not end of string
6c95: a0 ff                        ldy     #$ff              ;reached end of string, reset stuff
6c97: 84 27                        sty     txsc_char_delay   ;set to $ff
6c99: c8                           iny
6c9a: 84 26                        sty     txsc_msg_offset   ;set to $00
                   ; Draw char in bottom-right corner.
6c9c: 20 9d 69     DrawTxscChar2   jsr     GetGlyphPtr       ;get ptr in $0d-0e
6c9f: a9 fc                        lda     #252              ;X pos (left pixels will be at X=248)
6ca1: 85 00                        sta     ]xc
6ca3: a9 03                        lda     #3                ;Y pos (top pixels will be at Y=7)
6ca5: 85 01                        sta     ]yc
6ca7: a9 00                        lda     #$00
6ca9: 85 08                        sta     preserve_bg_flag  ;don't preserve background
6cab: a9 00                        lda     #$00              ;fg = color 0
6cad: 85 0b                        sta     draw_fg_color
6caf: a9 20                        lda     #%00100000        ;bg = color 1
6cb1: 85 0c                        sta     draw_bg_color
6cb3: 4c 27 66                     jmp     DrawGlyphPtrNoSc  ;draw single glyph

                   ; 
                   ; Sets the 3rd pixel bit for all pixels in the bottom 8 lines of the screen. 
                   ; Does not affect the 2bpp memory.
                   ; 
                   ; This area is used for the LOW / OUT messages during the game, and scrolling
                   ; text in "attract" mode.  The effect of this function is to erase the area to
                   ; the same color used for the city backdrop.
                   ; 
6cb6: a0 00        SetBottomBit3   ldy     #$00
6cb8: a9 ff                        lda     #$ff              ;set all bits
6cba: 99 01 04     :Loop           sta     VIDEO_RAM_3+$201,y ;lines 223-226
6cbd: 99 01 05                     sta     VIDEO_RAM_3+$301,y ;lines 227-230
6cc0: c8                           iny                       ;increment twice, because even-numbered bytes
6cc1: c8                           iny                       ; affect different lines
6cc2: d0 f6                        bne     :Loop
6cc4: 60                           rts

                   ; 
                   ; Message glyph size stretch factor, stored as $XY.  X is the horizontal
                   ; multiplier, Y is the vertical multiplier.
                   ; 
                   ; For example, the "MISSILE COMMAND" logo (messages $1c/$1d) uses $44 to expand
                   ; the glyphs 4x vertically and horizontally, while "THE END" (message $08) is
                   ; stretched 3x horizontally and 10x vertically.
                   msg_glyph_size_tbl
6cc5: 11 11 11 11+                 .bulk   $11,$11,$11,$11,$11,$11,$11,$11
6ccd: 3a 21 11 11+                 .bulk   $3a,$21,$11,$11,$11,$11,$11,$11
6cd5: 11 11 11 11+                 .bulk   $11,$11,$11,$11,$11,$11,$11,$11
6cdd: 11 11 11 11+                 .bulk   $11,$11,$11,$11,$44,$44,$11,$11
6ce5: 11 11 11 11+                 .bulk   $11,$11,$11,$11,$11,$11,$11,$11
                   ; 
                   ; Message vertical position.  Specifies the top position.  Note 0 is at the
                   ; bottom of the screen.
                   ; 
                   ; (Drawing off the bottom of the screen will cause the system to reset.)
                   msg_vertical_tbl
6ced: 90 d0 03 03+                 .bulk   $90,$d0,$03,$03,$03,$40,$70,$a0
6cf5: a0 80 40 40+                 .bulk   $a0,$80,$40,$40,$40,$03,$a0,$03
6cfd: 03 03 70 60+                 .bulk   $03,$03,$70,$60,$50,$38,$03,$03
6d05: 03 58 58 58+                 .bulk   $03,$58,$58,$58,$a0,$70,$03,$70
6d0d: 90 70 90 80+                 .bulk   $90,$70,$90,$80,$80,$40,$34,$80
                   ; 
6d15: 95                           .dd1    $95               ;checksum byte
                   ; 
                   ; Pointers to message strings.  Each entry is 3 bytes: the horizontal screen
                   ; offset, followed by a 16-bit pointer to the text.  The vertical position and
                   ; size multipliers for each message come from the tables above.
                   ; 
6d16: 60           msgs_fr         .dd1    $60               ;French
6d17: d2 6f                        .dd2    L6FD2
6d19: 40                           .dd1    $40
6d1a: bb 70                        .dd2    L70BB
6d1c: 06                           .dd1    $06
6d1d: d8 6f                        .dd2    L6FD8
6d1f: 90                           .dd1    $90
6d20: e5 6f                        .dd2    L6FE5
6d22: 78                           .dd1    $78
6d23: f6 6f                        .dd2    L6FF6
6d25: 5c                           .dd1    $5c
6d26: 0b 70                        .dd2    L700B
6d28: 68                           .dd1    $68
6d29: 22 70                        .dd2    L7022
6d2b: 50                           .dd1    $50
6d2c: 16 70                        .dd2    L7016
6d2e: 60                           .dd1    $60
6d2f: 2a 70                        .dd2    L702A
6d31: 10                           .dd1    $10
6d32: 5f 70                        .dd2    L705F
6d34: 40                           .dd1    $40
6d35: 2d 70                        .dd2    L702D
6d37: 40                           .dd1    $40
6d38: 3e 70                        .dd2    L703E
6d3a: 40                           .dd1    $40
6d3b: 4e 70                        .dd2    L704E
6d3d: b0                           .dd1    $b0
6d3e: f6 6e                        .dd2    L6EF6
6d40: 54                           .dd1    $54
6d41: d8 6f                        .dd2    L6FD8
6d43: 04                           .dd1    $04
6d44: e1 70                        .dd2    L70E1
6d46: 70                           .dd1    $70
6d47: e1 70                        .dd2    L70E1
6d49: e4                           .dd1    $e4
6d4a: e1 70                        .dd2    L70E1
6d4c: 24                           .dd1    $24
6d4d: 6e 70                        .dd2    L706E
6d4f: 04                           .dd1    $04
6d50: 86 70                        .dd2    L7086
6d52: 04                           .dd1    $04
6d53: a1 70                        .dd2    L70A1
6d55: 34                           .dd1    $34
6d56: cb 70                        .dd2    L70CB
6d58: 0c                           .dd1    $0c
6d59: de 70                        .dd2    L70DE
6d5b: 74                           .dd1    $74
6d5c: de 70                        .dd2    L70DE
6d5e: e8                           .dd1    $e8
6d5f: de 70                        .dd2    L70DE
6d61: 88                           .dd1    $88
6d62: e5 70                        .dd2    L70E5
6d64: c8                           .dd1    $c8
6d65: 24 70                        .dd2    L7022+2
6d67: 28                           .dd1    $28
6d68: 0b 70                        .dd2    L700B
6d6a: 54           msgs_de         .dd1    $54               ;German
6d6b: e6 70                        .dd2    L70E6
6d6d: 50                           .dd1    $50
6d6e: d0 71                        .dd2    L71D0
6d70: 06                           .dd1    $06
6d71: ed 70                        .dd2    L70ED
6d73: 90                           .dd1    $90
6d74: f6 70                        .dd2    L70F6
6d76: 78                           .dd1    $78
6d77: 09 71                        .dd2    L7109
6d79: 5c                           .dd1    $5c
6d7a: 17 71                        .dd2    L7117
6d7c: 68                           .dd1    $68
6d7d: 2c 71                        .dd2    L712C
6d7f: 58                           .dd1    $58
6d80: 21 71                        .dd2    L7121
6d82: 54                           .dd1    $54
6d83: 34 71                        .dd2    L7134
6d85: 14                           .dd1    $14
6d86: 6e 71                        .dd2    L716E
6d88: 40                           .dd1    $40
6d89: 38 71                        .dd2    L7138
6d8b: 40                           .dd1    $40
6d8c: 47 71                        .dd2    L7147
6d8e: 40                           .dd1    $40
6d8f: 56 71                        .dd2    L7156
6d91: b0                           .dd1    $b0
6d92: 66 71                        .dd2    L7166
6d94: 58                           .dd1    $58
6d95: ed 70                        .dd2    L70ED
6d97: 04                           .dd1    $04
6d98: f7 71                        .dd2    L71F7
6d9a: 70                           .dd1    $70
6d9b: f7 71                        .dd2    L71F7
6d9d: e4                           .dd1    $e4
6d9e: f7 71                        .dd2    L71F7
6da0: 14                           .dd1    $14
6da1: 7c 71                        .dd2    L717C
6da3: 04                           .dd1    $04
6da4: 98 71                        .dd2    L7198
6da6: 04                           .dd1    $04
6da7: b7 71                        .dd2    L71B7
6da9: 30                           .dd1    $30
6daa: dc 71                        .dd2    L71DC
6dac: 04                           .dd1    $04
6dad: f2 71                        .dd2    L71F2
6daf: 6c                           .dd1    $6c
6db0: f2 71                        .dd2    L71F2
6db2: dc                           .dd1    $dc
6db3: f2 71                        .dd2    L71F2
6db5: 70                           .dd1    $70
6db6: fb 71                        .dd2    L71FB
6db8: c8                           .dd1    $c8
6db9: 2e 71                        .dd2    L712C+2
6dbb: 18                           .dd1    $18
6dbc: 17 71                        .dd2    L7117
6dbe: 5c           msgs_es         .dd1    $5c               ;Spanish
6dbf: ff 71                        .dd2    L71FF
6dc1: 64                           .dd1    $64
6dc2: eb 72                        .dd2    L72EB
6dc4: 06                           .dd1    $06
6dc5: 06 72                        .dd2    L7206
6dc7: 90                           .dd1    $90
6dc8: 15 72                        .dd2    L7215
6dca: 78                           .dd1    $78
6dcb: 21 72                        .dd2    L7221
6dcd: 5c                           .dd1    $5c
6dce: 2f 72                        .dd2    L722F
6dd0: 68                           .dd1    $68
6dd1: 51 72                        .dd2    L7251
6dd3: 30                           .dd1    $30
6dd4: 3b 72                        .dd2    L723B
6dd6: 60                           .dd1    $60
6dd7: 2a 70                        .dd2    L702A
6dd9: 24                           .dd1    $24
6dda: 94 72                        .dd2    L7294
6ddc: 40                           .dd1    $40
6ddd: 59 72                        .dd2    L7259
6ddf: 40                           .dd1    $40
6de0: 6a 72                        .dd2    L726A
6de2: 40                           .dd1    $40
6de3: 7a 72                        .dd2    L727A
6de5: b0                           .dd1    $b0
6de6: 8b 72                        .dd2    L728B
6de8: 48                           .dd1    $48
6de9: 06 72                        .dd2    L7206
6deb: 0c                           .dd1    $0c
6dec: 0b 73                        .dd2    L730B
6dee: 74                           .dd1    $74
6def: 0b 73                        .dd2    L730B
6df1: e8                           .dd1    $e8
6df2: 0b 73                        .dd2    L730B
6df4: 38                           .dd1    $38
6df5: a0 72                        .dd2    L72A0
6df7: 04                           .dd1    $04
6df8: b3 72                        .dd2    L72B3
6dfa: 04                           .dd1    $04
6dfb: d3 72                        .dd2    L72D3
6dfd: 30                           .dd1    $30
6dfe: f2 72                        .dd2    L72F2
6e00: 04                           .dd1    $04
6e01: 07 73                        .dd2    L7307
6e03: 70                           .dd1    $70
6e04: 07 73                        .dd2    L7307
6e06: e4                           .dd1    $e4
6e07: 07 73                        .dd2    L7307
6e09: 70                           .dd1    $70
6e0a: 0e 73                        .dd2    L730E
6e0c: c8                           .dd1    $c8
6e0d: 53 72                        .dd2    L7251+2
6e0f: 08                           .dd1    $08
6e10: 2f 72                        .dd2    L722F
6e12: 60           msgs_en         .dd1    96                ;$00 "PLAYER"
6e13: 8a 6e                        .dd2    L6E8A
6e15: 54                           .dd1    84                ;$01 "HIGH SCORES"
6e16: 78 6f                        .dd2    L6F78
6e18: 06                           .dd1    6                 ;$02 "GAME OVER"
6e19: 90 6e                        .dd2    L6E90
6e1b: 90                           .dd1    144               ;$03 "PRESS START"
6e1c: 99 6e                        .dd2    L6E99
6e1e: 78                           .dd1    120               ;$04 "INSERT COINS"
6e1f: a4 6e                        .dd2    L6EA4
6e21: 5c                           .dd1    92                ;$05 "BONUS CITY"
6e22: b0 6e                        .dd2    L6EB0
6e24: 68                           .dd1    104               ;$06 "X POINTS"
6e25: 22 70                        .dd2    L7022
6e27: 50                           .dd1    80                ;$07 "BONUS POINTS"
6e28: ba 6e                        .dd2    L6EBA
6e2a: 36                           .dd1    54                ;$08 "THE END"
6e2b: c6 6e                        .dd2    L6EC6
6e2d: 2c                           .dd1    44                ;$09 "GREAT SCORE"
6e2e: 20 6f                        .dd2    L6F20
6e30: 40                           .dd1    64                ;$0a "1 COIN 2 PLAYS"
6e31: cd 6e                        .dd2    L6ECD
6e33: 40                           .dd1    64                ;$0b "1 COIN 1 PLAY"
6e34: db 6e                        .dd2    L6EDB
6e36: 40                           .dd1    64                ;$0c "2 COINS 1 PLAY"
6e37: e8 6e                        .dd2    L6EE8
6e39: b0                           .dd1    176               ;$0d "CREDITS:"
6e3a: f6 6e                        .dd2    L6EF6
6e3c: 5c                           .dd1    92                ;$0e "GAME OVER"
6e3d: 90 6e                        .dd2    L6E90
6e3f: 0c                           .dd1    12                ;$0f "OUT"
6e40: a6 6f                        .dd2    L6FA6
6e42: 74                           .dd1    116               ;$10 "OUT"
6e43: a6 6f                        .dd2    L6FA6
6e45: e8                           .dd1    232               ;$11 "OUT"
6e46: a6 6f                        .dd2    L6FA6
6e48: 38                           .dd1    56                ;$12 "ENTER YOUR INITIALS"
6e49: 2b 6f                        .dd2    L6F2B
6e4b: 04                           .dd1    4                 ;$13 "SPIN BALL TO CHANGE LETTERS"
6e4c: 3e 6f                        .dd2    L6F3E
6e4e: 04                           .dd1    4                 ;$14 "PRESS ANY FIRE SWITCH TO SELECT"
6e4f: 59 6f                        .dd2    L6F59
6e51: 38                           .dd1    56                ;$15 "DEFEND CITIES"
6e52: 83 6f                        .dd2    L6F83
6e54: 0c                           .dd1    12                ;$16 "LOW"
6e55: 96 6f                        .dd2    L6F96
6e57: 74                           .dd1    116               ;$17 "LOW"
6e58: 96 6f                        .dd2    L6F96
6e5a: e8                           .dd1    232               ;$18 "LOW"
6e5b: 96 6f                        .dd2    L6F96
6e5d: 68                           .dd1    104               ;$19 "EVERY"
6e5e: c4 6f                        .dd2    L6FC4
6e60: c8                           .dd1    200               ;$1a "POINTS"
6e61: 24 70                        .dd2    L7022+2
6e63: 10                           .dd1    16                ;$1b "BONUS CITY"
6e64: b0 6e                        .dd2    L6EB0
                   ; Start of non-localized messages.
6e66: 20                           .dd1    32                ;$1c "MISSILE"
6e67: b6 6f                        .dd2    L6FB6
6e69: 20                           .dd1    32                ;$1d "COMMAND"
6e6a: bd 6f                        .dd2    L6FBD
6e6c: 50                           .dd1    80                ;$1e "ATARI (C)(P) 1980"
6e6d: a9 6f                        .dd2    L6FA9
6e6f: 5c                           .dd1    92                ;$1f "RAM OK"
6e70: fe 6e                        .dd2    L6EFE
6e72: 5c                           .dd1    92                ;$20 "ROM OK"
6e73: 04 6f                        .dd2    L6F04
6e75: 5c                           .dd1    92                ;$21 "BAD RAM"
6e76: 0a 6f                        .dd2    L6F0A
6e78: 5c                           .dd1    92                ;$22 "BAD ROM"
6e79: 11 6f                        .dd2    L6F11
6e7b: 5c                           .dd1    92                ;$23 "BAD MAP"
6e7c: 9f 6f                        .dd2    L6F9F
6e7e: 5c                           .dd1    92                ;$24 "MAP OK"
6e7f: 99 6f                        .dd2    L6F99
6e81: 40                           .dd1    64                ;$25 "FREE PLAY"
6e82: c9 6f                        .dd2    L6FC9
6e84: 50                           .dd1    80                ;$26 "CITIES"
6e85: 90 6f                        .dd2    L6F83+13
6e87: 10                           .dd1    16                ;$27 "BAD CHIP"
6e88: 18 6f                        .dd2    L6F18
                   ; Localized strings, starting with English.
6e8a: 50 4c 41 59+ L6E8A           .dstr   ‘PLAYER’
6e90: 47 41 4d 45+ L6E90           .dstr   ‘GAME OVER’
6e99: 50 52 45 53+ L6E99           .dstr   ‘PRESS START’
6ea4: 49 4e 53 45+ L6EA4           .dstr   ‘INSERT COINS’
6eb0: 42 4f 4e 55+ L6EB0           .dstr   ‘BONUS CITY’
6eba: 42 4f 4e 55+ L6EBA           .dstr   ‘BONUS POINTS’
6ec6: 54 48 45 20+ L6EC6           .dstr   ‘THE END’
6ecd: 31 20 43 4f+ L6ECD           .dstr   ‘1 COIN 2 PLAYS’
6edb: 31 20 43 4f+ L6EDB           .dstr   ‘1 COIN 1 PLAY’
6ee8: 32 20 43 4f+ L6EE8           .dstr   ‘2 COINS 1 PLAY’
6ef6: 43 52 45 44+ L6EF6           .dstr   ‘CREDITS:’
6efe: 52 41 4d 20+ L6EFE           .dstr   ‘RAM OK’
6f04: 52 4f 4d 20+ L6F04           .dstr   ‘ROM OK’
6f0a: 42 41 44 20+ L6F0A           .dstr   ‘BAD RAM’
6f11: 42 41 44 20+ L6F11           .dstr   ‘BAD ROM’
6f18: 42 41 44 20+ L6F18           .dstr   ‘BAD CHIP’
6f20: 47 52 45 41+ L6F20           .dstr   ‘GREAT SCORE’
6f2b: 45 4e 54 45+ L6F2B           .dstr   ‘ENTER YOUR INITIALS’
6f3e: 53 50 49 4e+ L6F3E           .dstr   ‘SPIN BALL TO CHANGE LETTERS’
6f59: 50 52 45 53+ L6F59           .dstr   ‘PRESS ANY FIRE SWITCH TO SELECT’
6f78: 48 49 47 48+ L6F78           .dstr   ‘HIGH SCORES’
6f83: 44 45 46 45+ L6F83           .dstr   ‘DEFEND       CITIES’
6f96: 4c 4f d7     L6F96           .dstr   ‘LOW’
6f99: 4d 41 50 20+ L6F99           .dstr   ‘MAP OK’
6f9f: 42 41 44 20+ L6F9F           .dstr   ‘BAD MAP’
6fa6: 4f 55 d4     L6FA6           .dstr   ‘OUT’
6fa9: 41 54 41 52+ L6FA9           .dstr   ‘ATARI @? 1980’
6fb6: 4d 49 53 53+ L6FB6           .dstr   ‘MISSILE’
6fbd: 43 4f 4d 4d+ L6FBD           .dstr   ‘COMMAND’
6fc4: 45 56 45 52+ L6FC4           .dstr   ‘EVERY’
6fc9: 46 52 45 45+ L6FC9           .dstr   ‘FREE PLAY’
                   ; French
6fd2: 4a 4f 55 45+ L6FD2           .dstr   ‘JOUEUR’
6fd8: 46 49 4e 20+ L6FD8           .dstr   ‘FIN DE PARTIE’
6fe5: 41 50 50 55+ L6FE5           .dstr   ‘APPUYEZ SUR START’
6ff6: 49 4e 54 52+ L6FF6           .dstr   ‘INTRODUIRE LES PIECES’
700b: 42 4f 4e 55+ L700B           .dstr   ‘BONUS VILLE’
7016: 42 4f 4e 55+ L7016           .dstr   ‘BONUS POINTS’
7022: 58 20 50 4f+ L7022           .dstr   ‘X POINTS’
702a: 46 49 ce     L702A           .dstr   ‘FIN’
702d: 31 20 50 49+ L702D           .dstr   ‘1 PIECE 2 JOUEURS’
703e: 31 20 50 49+ L703E           .dstr   ‘1 PIECE 1 JOUEUR’
704e: 32 20 50 49+ L704E           .dstr   ‘2 PIECES 1 JOUEUR’
705f: 53 50 4c 45+ L705F           .dstr   ‘SPLENDIDE SCORE’
706e: 53 56 50 20+ L706E           .dstr   ‘SVP ENTREZ VOS INITIALES’
7086: 54 4f 55 52+ L7086           .dstr   ‘TOURNEZ BOULE POUR INITIALE’
70a1: 50 4f 55 53+ L70A1           .dstr   ‘POUSSEZ FEU QUAND CORRECTE’
70bb: 4d 45 49 4c+ L70BB           .dstr   ‘MEILLEURS SCORES’
70cb: 44 45 46 45+ L70CB           .dstr   ‘DEFENSEZ     VILLES’
70de: 42 41 d3     L70DE           .dstr   ‘BAS’
70e1: 56 49 44 c5  L70E1           .dstr   ‘VIDE’
70e5: c1           L70E5           .dstr   ‘A’
                   ; German
70e6: 53 50 49 45+ L70E6           .dstr   ‘SPIELER’
70ed: 53 50 49 45+ L70ED           .dstr   ‘SPIELENDE’
70f6: 53 54 41 52+ L70F6           .dstr   ‘STARTKN$PFE DR%CKEN’
7109: 47 45 4c 44+ L7109           .dstr   ‘GELD EINWERFEN’
7117: 42 4f 4e 55+ L7117           .dstr   ‘BONUSSTADT’
7121: 42 4f 4e 55+ L7121           .dstr   ‘BONUSPUNKTE’
712c: 58 20 50 55+ L712C           .dstr   ‘X PUNKTE’
7134: 45 4e 44 c5  L7134           .dstr   ‘ENDE’
7138: 31 20 4d 25+ L7138           .dstr   ‘1 M%NZ 2 SPIELE’
7147: 31 20 4d 25+ L7147           .dstr   ‘1 M%NZE 1 SPIEL’
7156: 32 20 4d 25+ L7156           .dstr   ‘2 M%NZEN 1 SPIEL’
7166: 4b 52 45 44+ L7166           .dstr   ‘KREDITE:’
716e: 50 52 49 4d+ L716E           .dstr   ‘PRIMA ERGEBNIS’
717c: 47 45 42 45+ L717C           .dstr   ‘GEBEN SIE IHRE INITIALEN EIN’
7198: 42 41 4c 4c+ L7198           .dstr   ‘BALL DREHEN F%R ALLE BUCHSTABEN’
71b7: 46 49 52 45+ L71B7           .dstr   ‘FIRE DR%CKEN WENN RICHTIG’
71d0: 48 24 43 48+ L71D0           .dstr   ‘H$CHSTZAHLEN’
71dc: 53 54 23 44+ L71DC           .dstr   ‘ST#DTE     VERTEIDIGEN’
71f2: 57 45 4e 49+ L71F2           .dstr   ‘WENIG’
71f7: 4c 45 45 d2  L71F7           .dstr   ‘LEER’
71fb: 4a 45 44 c5  L71FB           .dstr   ‘JEDE’
                   ; Spanish
71ff: 4a 55 47 41+ L71FF           .dstr   ‘JUGADOR’
7206: 4a 55 45 47+ L7206           .dstr   ‘JUEGO TERMINADO’
7215: 50 55 4c 53+ L7215           .dstr   ‘PULSAR START’
7221: 49 4e 53 45+ L7221           .dstr   ‘INSERTE FICHAS’
722f: 43 49 55 44+ L722F           .dstr   ‘CIUDAD EXTRA’
723b: 42 4f 4e 49+ L723B           .dstr   ‘BONIFICACION DE PUNTOS’
7251: 58 20 50 55+ L7251           .dstr   ‘X PUNTOS’
7259: 31 20 4d 4f+ L7259           .dstr   ‘1 MONEDA 2 JUEGOS’
726a: 31 20 4d 4f+ L726A           .dstr   ‘1 MONEDA 1 JUEGO’
727a: 32 20 4d 4f+ L727A           .dstr   ‘2 MONEDAS 1 JUEGO’
728b: 43 52 45 44+ L728B           .dstr   ‘CREDITOS:’
7294: 47 52 41 4e+ L7294           .dstr   ‘GRAN PUNTAJE’
72a0: 45 4e 54 52+ L72A0           .dstr   ‘ENTRE SUS INICIALES’
72b3: 47 49 52 45+ L72B3           .dstr   ‘GIRE LA BOLA PARA CAMBIAR LETRAS’
72d3: 4f 50 52 49+ L72D3           .dstr   ‘OPRIMA FIRE POR LA LETRA’
72eb: 52 45 43 4f+ L72EB           .dstr   ‘RECORDS’
72f2: 44 45 46 49+ L72F2           .dstr   ‘DEFIENDA     CIUDADES’
7307: 50 4f 43 cf  L7307           .dstr   ‘POCO’
730b: 53 49 ce     L730B           .dstr   ‘SIN’
730e: 43 41 44 c1  L730E           .dstr   ‘CADA’

                   ; 
                   ; 8x8 1-bit bitmaps, primarily font glyphs.  The screen is organized with line 0
                   ; at the bottom, and the glyphs are stored in the same order.  The characters
                   ; leave a blank row at the top and a blank column on the right.
                   ; 
                   ; 
                   ; Blank bitmap, used for unknown characters and suppressed leading '0's.
                   ; 
                   vis
7312: 00 00 00 00+ glyphs_4        .bulk   $00,$00,$00,$00,$00,$00,$00,$00

                   ; 
                   ; Glyphs '0' - '9'.
                   vis
731a: 38 44 c6 c6+ glyphs_0        .bulk   $38,$44,$c6,$c6,$c6,$44,$38,$00
7322: fc 30 30 30+                 .bulk   $fc,$30,$30,$30,$30,$70,$30,$00
732a: fe e0 78 3c+                 .bulk   $fe,$e0,$78,$3c,$0e,$c6,$7c,$00
7332: 7c c6 06 3c+                 .bulk   $7c,$c6,$06,$3c,$18,$0c,$7e,$00
733a: 0c 0c fe cc+                 .bulk   $0c,$0c,$fe,$cc,$6c,$3c,$1c,$00
7342: 7c c6 06 06+                 .bulk   $7c,$c6,$06,$06,$fc,$c0,$fc,$00
734a: 7c c6 c6 fc+                 .bulk   $7c,$c6,$c6,$fc,$c0,$60,$3c,$00
7352: 30 30 30 18+                 .bulk   $30,$30,$30,$18,$0c,$c6,$fe,$00
735a: 7c 86 9e 78+                 .bulk   $7c,$86,$9e,$78,$e4,$c4,$78,$00
7362: 78 0c 06 7e+ glyphs_2        .bulk   $78,$0c,$06,$7e,$c6,$c6,$7c,$00

                   ; 
                   ; Glyphs 'A' - 'Z'.  Technically it's '@' - 'Z', but '@' gets remapped to a
                   ; separate table, which is why the table's label is on the previous glyph.
                   vis
736a: c6 c6 fe c6+                 .bulk   $c6,$c6,$fe,$c6,$c6,$6c,$38,$00 ;'A'=1
7372: fc c6 c6 fc+                 .bulk   $fc,$c6,$c6,$fc,$c6,$c6,$fc,$00
737a: 3c 66 c0 c0+                 .bulk   $3c,$66,$c0,$c0,$c0,$66,$3c,$00
7382: f8 cc c6 c6+                 .bulk   $f8,$cc,$c6,$c6,$c6,$cc,$f8,$00
738a: fc c0 c0 f8+                 .bulk   $fc,$c0,$c0,$f8,$c0,$c0,$fc,$00
7392: c0 c0 c0 fc+                 .bulk   $c0,$c0,$c0,$fc,$c0,$c0,$fe,$00
739a: 3e 66 c6 ce+                 .bulk   $3e,$66,$c6,$ce,$c0,$60,$3e,$00
73a2: c6 c6 c6 fe+                 .bulk   $c6,$c6,$c6,$fe,$c6,$c6,$c6,$00
73aa: fc 30 30 30+                 .bulk   $fc,$30,$30,$30,$30,$30,$fc,$00
73b2: 7c c6 06 06+                 .bulk   $7c,$c6,$06,$06,$06,$06,$06,$00
73ba: ce dc f8 f0+                 .bulk   $ce,$dc,$f8,$f0,$d8,$cc,$c6,$00
73c2: fc c0 c0 c0+                 .bulk   $fc,$c0,$c0,$c0,$c0,$c0,$c0,$00
73ca: c6 c6 d6 fe+                 .bulk   $c6,$c6,$d6,$fe,$fe,$ee,$c6,$00
73d2: c6 ce de fe+                 .bulk   $c6,$ce,$de,$fe,$f6,$e6,$c6,$00
73da: 7c c6 c6 c6+                 .bulk   $7c,$c6,$c6,$c6,$c6,$c6,$7c,$00
73e2: c0 c0 fc c6+                 .bulk   $c0,$c0,$fc,$c6,$c6,$c6,$fc,$00
73ea: 7a cc de c6+                 .bulk   $7a,$cc,$de,$c6,$c6,$c6,$7c,$00
73f2: ce dc f8 ce+                 .bulk   $ce,$dc,$f8,$ce,$c6,$c6,$fc,$00
73fa: 7c c6 06 7c+                 .bulk   $7c,$c6,$06,$7c,$c0,$c6,$7c,$00
7402: 30 30 30 30+                 .bulk   $30,$30,$30,$30,$30,$30,$fc,$00
740a: 7c c6 c6 c6+                 .bulk   $7c,$c6,$c6,$c6,$c6,$c6,$c6,$00
7412: 10 38 6c ee+                 .bulk   $10,$38,$6c,$ee,$c6,$c6,$c6,$00
741a: c6 ee fe fe+                 .bulk   $c6,$ee,$fe,$fe,$d6,$c6,$c6,$00
7422: c6 ee 7c 38+                 .bulk   $c6,$ee,$7c,$38,$7c,$ee,$c6,$00
742a: 30 30 30 78+                 .bulk   $30,$30,$30,$78,$cc,$cc,$cc,$00
7432: fe e0 70 38+                 .bulk   $fe,$e0,$70,$38,$1c,$0e,$fe,$00 ;'Z'=26

                   ; 
                   ; Glyphs for symbols.  ASCII ' ' and ':' are represented, and a few ('#', '$',
                   ; '%') are remapped to A/O/U with umlauts.  The latter can be found in the
                   ; German-language strings.
                   ; 
                   ; The arrows used to indicate player 1 vs. player 2 are here, as is the downward
                   ; arrow for the "protect cities" instruction.
                   ; 
                   ; This also has a set of four entries for the cities.  The two "far" entries are
                   ; drawn first in color #7, then partially drawn over by the "near" entries in
                   ; color #3.
                   vis
743a: 00 00 00 00+ glyphs_6        .bulk   $00,$00,$00,$00,$00,$00,$00,$00 ;' ' (#27) - duplicate of entry in glyphs_4
7442: 18 3c 7e ff+                 .bulk   $18,$3c,$7e,$ff,$ff,$bd,$3c,$3c ;down arrow (#28)
744a: 7f ff 7f 7e+                 .bulk   $7f,$ff,$7f,$7e,$34,$30,$20,$00 ;city left/far
7452: 7c 37 25 00+                 .bulk   $7c,$37,$25,$00,$00,$00,$00,$00 ;city left/near
745a: fe ff fe fe+                 .bulk   $fe,$ff,$fe,$fe,$fa,$50,$10,$10 ;city right/far
7462: 3e 6c c8 c0+                 .bulk   $3e,$6c,$c8,$c0,$80,$00,$00,$00 ;city right/near
746a: 1c 38 7f ff+                 .bulk   $1c,$38,$7f,$ff,$ff,$7f,$38,$1c ;left arrow (#33)
7472: 38 1c fe ff+                 .bulk   $38,$1c,$fe,$ff,$ff,$fe,$1c,$38 ;right arrow (#34)
747a: 3c 42 99 91+                 .bulk   $3c,$42,$99,$91,$99,$42,$3c,$00 ;'@' -> (C) copyright symbol
7482: 30 30 00 30+                 .bulk   $30,$30,$00,$30,$30,$00,$00,$00 ;':'
748a: 3c 52 9d 95+                 .bulk   $3c,$52,$9d,$95,$9d,$42,$3c,$00 ;'?' -> (P) sound copyright symbol
7492: c6 fe c6 6c+                 .bulk   $c6,$fe,$c6,$6c,$38,$10,$44,$00 ;'#' -> A + umlaut
749a: 7c c6 c6 c6+                 .bulk   $7c,$c6,$c6,$c6,$7c,$00,$28,$00 ;'$' -> O + umlaut
74a2: 7c c6 c6 c6+                 .bulk   $7c,$c6,$c6,$c6,$c6,$00,$28,$00 ;'%' -> U + umlaut

                   ; 
                   ; Prints "bonus city every N points".  If the machine is configured for no bonus
                   ; cities, nothing is displayed.
                   ; 
                   PrintBonusCityPts
74aa: a5 f4                        lda     r8_irq_inv        ;get R8 switches
74ac: 29 70                        and     #%01110000        ;mask to get the bonus city setting
74ae: c9 70                        cmp     #%01110000        ;are bonus cities disabled?
74b0: f0 2e                        beq     :Return           ;yes, bail
74b2: a9 1b                        lda     #$1b
74b4: 20 51 6a                     jsr     PrintMessage      ;"BONUS CITY"
74b7: a9 19                        lda     #$19
74b9: 20 51 6a                     jsr     PrintMessage      ;"EVERY"
74bc: a9 1a                        lda     #$1a
74be: 20 51 6a                     jsr     PrintMessage      ;"POINTS"
74c1: a9 00                        lda     #$00
74c3: 85 d1                        sta     bcd3_acc          ;set low byte of score to zero
74c5: a5 f4                        lda     r8_irq_inv        ;get R8 switches
74c7: 29 70                        and     #%01110000        ;mask to get the bonus city setting
74c9: 4a                           lsr     A
74ca: 4a                           lsr     A
74cb: 4a                           lsr     A                 ;convert to 0-15 (even)
74cc: aa                           tax                       ;use as index
74cd: bd 82 60                     lda     bonus_city_bcd,x  ;get middle byte of score
74d0: 85 d2                        sta     bcd3_acc+1        ;copy to BCD accumulator
74d2: bd 83 60                     lda     bonus_city_bcd+1,x ;get high byte of score
74d5: 85 d3                        sta     bcd3_acc+2        ;copy to BCD acc
74d7: a2 90                        ldx     #144              ;horizontal position
74d9: a0 58                        ldy     #88               ;vertical position
74db: a9 40                        lda     #%01000000        ;fg color = 2
74dd: 20 f6 6a                     jsr     PrintBcdNumber    ;display number
74e0: 60           :Return         rts

                   ; 
                   ; Initializes high score list to defaults.
                   ; 
74e1: a2 17        InitDefScores   ldx     #23               ;8 entries * 3 bytes
74e3: bd f3 74     :Loop           lda     :def_score_names,x
74e6: 9d 2c 00                     sta:    hscore_initials,x ;copy initials
74e9: bd 0b 75                     lda     :def_scores,x
74ec: 9d 44 00                     sta:    hscore_scores,x   ;copy scores
74ef: ca                           dex
74f0: 10 f1                        bpl     :Loop
74f2: 60                           rts

                   ; 
                   ; Default high scores, from lowest to highest.  Initials are ASCII, scores are
                   ; 3-byte BCD.  For example, the highest default score is DFT with 7500 points.
                   ; 
                   ; cf. https://arcadeblogger.com/2021/01/31/anatomy-of-arcade-high-score-tables/
                   ; 
                   :def_score_names
74f3: 47 4a 4c 44+                 .dstr   ‘GJLDEWJEDMJPRDASRCDLSDFT’
750b: 50 69 00     :def_scores     .dd3    $006950           ;GJL - Gerry Lichac
750e: 05 70 00                     .dd3    $007005           ;DEW - Dave Wiebenson
7511: 50 71 00                     .dd3    $007150           ;JED - Jed Margolin
7514: 00 72 00                     .dd3    $007200           ;MJP - Mary Pepper
7517: 50 72 00                     .dd3    $007250           ;RDA - Rich D. Adam
751a: 30 73 00                     .dd3    $007330           ;SRC - Steve Calfee
751d: 95 74 00                     .dd3    $007495           ;DLS - Dave Sherman
7520: 00 75 00                     .dd3    $007500           ;DFT - Dave F. Theurer

                   ; 
                   ; Prints the highest score, then prints the scores for the two players, at the
                   ; top of the screen.
                   ; 
7523: ad 59 00     PrintTopScores  lda:    hscore_scores+21  ;low score appears first, so grab the score from
7526: 85 d1                        sta     bcd3_acc          ; the end of the list
7528: ad 5a 00                     lda:    hscore_scores+22
752b: 85 d2                        sta     bcd3_acc+1
752d: ad 5b 00                     lda:    hscore_scores+23
7530: 85 d3                        sta     bcd3_acc+2
7532: a2 64                        ldx     #100              ;a bit left of center
7534: a0 e2                        ldy     #226              ;near top of screen
7536: a9 40                        lda     #%01000000        ;fg color = 2
7538: 20 f6 6a                     jsr     PrintBcdNumber    ;print the score
753b: 4c e1 5f                     jmp     PrintScores       ;print scores for both players

753e: 22                           .dd1    $22               ;checksum byte

                   ; 
                   ; Func $18: checks to see if any high scores were beaten.
                   ; 
                   • Clear variables
                   ]new_high_flag  .var    $8d    {addr/1}

                   F_HighScoreCheck
753f: 20 e4 76                     jsr     ScrubHighScores   ;start with a clean slate
7542: a5 93                        lda     play_mode_flag    ;is a game in progress?
7544: d0 07                        bne     :CheckHigh        ;yes, branch
7546: a0 1c                        ldy     #FN_SHOW_HS       ;show high scores next
7548: 84 91                        sty     func_index
754a: b8                           clv
754b: 50 3d                        bvc     :Return

754d: a2 01        :CheckHigh      ldx     #$01              ;start with player 2
754f: 86 8d                        stx     ]new_high_flag    ;set to a positive value
                   ; Scores are stored from lowest to highest.  Starting with the highest, walk
                   ; through the list until we find a lower score.
7551: a0 15        :PlayerLoop     ldy     #21               ;7th entry, 3 bytes per entry
7553: b9 46 00     :CmpLoop        lda     hscore_scores+2,y ;see if our score beat this one
7556: dd da 01                     cmp     cur_score_hi,x    ;compare high byte
7559: d0 0e                        bne     :NotEq            ;not equal, branch
755b: b9 45 00                     lda     hscore_scores+1,y
755e: dd d8 01                     cmp     cur_score_md,x    ;compare mid byte
7561: d0 06                        bne     :NotEq            ;not equal, branch
7563: b9 44 00                     lda     hscore_scores,y
7566: dd d6 01                     cmp     cur_score_lo,x    ;compare low byte
7569: b0 03        :NotEq          bcs     :NotHigher        ;previous high is >= current, branch
756b: 20 8b 75                     jsr     InsertScore       ;insert score placeholder; sets Y-reg to $00
756e: 88           :NotHigher      dey                       ;move to next entry
756f: 88                           dey
7570: 88                           dey
7571: 10 e0                        bpl     :CmpLoop          ;if not done, loop
7573: ca                           dex                       ;next player
7574: 10 db                        bpl     :PlayerLoop       ;not done with players; loop
                   ; 
7576: 20 33 6c                     jsr     TxscResetStream   ;reset scrolling text
7579: a5 8d                        lda     ]new_high_flag    ;was this a new high score?
757b: 10 09                        bpl     :GoFunc06         ;no, bail
757d: 20 21 69                     jsr     SetColorPalette   ;set palette for current wave
7580: 20 e4 75                     jsr     PrepEnterInitials ;configure to input initials
7583: b8                           clv
7584: 50 04                        bvc     :Return           ;(always)

7586: a0 06        :GoFunc06       ldy     #FN_GAME_OVER     ;game over time
7588: 84 91                        sty     func_index
758a: 60           :Return         rts

                   ; 
                   ; Inserts the current score into the high-score table at the specified position.
                   ; 
                   ; The first score in the list is the lowest score, so we copy slot N+1 to slot N
                   ; until we open the correct slot.  The player number is written into the first
                   ; byte of the initials table, so the initial-input code can find it later.
                   ; 
                   ; On entry:
                   ;   X-reg: player number (0 or 1)
                   ;   Y-reg: score insertion slot x3
                   ; 
                   ; On exit:
                   ;   Y-reg: $00
                   ;   $8d: $ff
                   ; 
                   ]score_slot     .var    $98    {addr/1}

758b: 84 98        InsertScore     sty     ]score_slot       ;store insertion slot x3 in ZP
758d: a0 00                        ldy     #$00              ;start at lowest score
758f: c4 98        :InsertLoop     cpy     ]score_slot       ;is this the target slot?
7591: d0 27                        bne     :MoveDown         ;no, copy it to a lower slot
                   ; 
7593: bd da 01                     lda     cur_score_hi,x    ;copy player's score into high score table
7596: 99 46 00                     sta     hscore_scores+2,y
7599: bd d8 01                     lda     cur_score_md,x
759c: 99 45 00                     sta     hscore_scores+1,y
759f: bd d6 01                     lda     cur_score_lo,x
75a2: 99 44 00                     sta     hscore_scores,y
75a5: e8                           inx                       ;increment player number
75a6: 8a                           txa                       ;copy to A-reg
75a7: ca                           dex                       ;undo increment
75a8: 99 2c 00                     sta     hscore_initials,y ;save player number + 1 in first initial
75ab: a9 5b                        lda     #$5b              ;store an unprintable character in other two
75ad: 99 2d 00                     sta     hscore_initials+1,y
75b0: 99 2e 00                     sta     hscore_initials+2,y
75b3: a0 00                        ldy     #$00              ;zero Y-reg so caller exits loop
75b5: a9 ff                        lda     #$ff              ;set flag indicating we stored the score
75b7: 85 8d                        sta     ]new_high_flag
75b9: 60                           rts

75ba: b9 49 00     :MoveDown       lda     hscore_scores+5,y ;move score to lower slot
75bd: 99 46 00                     sta     hscore_scores+2,y
75c0: b9 48 00                     lda     hscore_scores+4,y
75c3: 99 45 00                     sta     hscore_scores+1,y
75c6: b9 47 00                     lda     hscore_scores+3,y
75c9: 99 44 00                     sta     hscore_scores,y
75cc: b9 31 00                     lda     hscore_initials+5,y ;copy initials too
75cf: 99 2e 00                     sta     hscore_initials+2,y
75d2: b9 30 00                     lda     hscore_initials+4,y
75d5: 99 2d 00                     sta     hscore_initials+1,y
75d8: b9 2f 00                     lda     hscore_initials+3,y
75db: 99 2c 00                     sta     hscore_initials,y
                   ; 
75de: c8                           iny                       ;advance to next slot
75df: c8                           iny
75e0: c8                           iny
75e1: 10 ac                        bpl     :InsertLoop       ;if not done, loop
75e3: 60                           rts

                   ; 
                   ; Prepares the system for input of player initials.  We may need to accept
                   ; initials from both players, so this is called again after initial entry is
                   ; complete for the first player.
                   ; 
                   ; We don't draw crosshairs while doing this, so we can use a couple bytes in the
                   ; crosshair backing store for temporary storage.
                   ; 
                   PrepEnterInitials
75e4: a0 15                        ldy     #$15
75e6: b9 2c 00     :Loop           lda     hscore_initials,y ;player number is stored in first byte
75e9: f0 40                        beq     :NextEntry        ;empty entry, branch
75eb: c9 03                        cmp     #$03              ;is it > 2?
75ed: b0 3c                        bcs     :NextEntry        ;yes, not a placeholder; branch
                   ; 
75ef: 85 b9                        sta     cur_plyr_num      ;save as player number (1/2)
75f1: c6 b9                        dec     cur_plyr_num      ;decrement to make it 0/1
75f3: 84 16                        sty     cross_back_store+7 ;save entry index
75f5: 20 80 69                     jsr     ClearScreenPartial ;clear top part of screen
75f8: 20 2f 67                     jsr     InitLangAndFlip   ;flip screen toward appropriate player
75fb: 20 48 5f                     jsr     PrintPlayerN      ;indicate which player is entering initials
75fe: 20 ce 65                     jsr     DrawPlyrIndic
7601: 20 23 75                     jsr     PrintTopScores    ;print scores for both players
7604: a9 09                        lda     #$09
7606: 20 51 6a                     jsr     PrintMessage      ;"GREAT SCORE"
7609: a9 12                        lda     #$12
760b: 20 51 6a                     jsr     PrintMessage      ;"ENTER YOUR INITIALS"
760e: a9 13                        lda     #$13
7610: 20 51 6a                     jsr     PrintMessage      ;"SPIN BALL ..."
7613: a9 14                        lda     #$14
7615: 20 51 6a                     jsr     PrintMessage      ;"PRESS ANY FIRE ..."
7618: a9 ff                        lda     #$ff              ;init timeout to 255 sets of 16 frames
761a: 85 0f                        sta     cross_back_store  ; (about 68 seconds)
761c: a2 00                        ldx     #$00
761e: 86 a5                        stx     cross_xc          ;set letter to 'A'
7620: 86 fa                        stx     fire_buttons      ;clear inputs
7622: a9 02                        lda     #$02
7624: 85 0a                        sta     last_cross_yc     ;set index to first initial
7626: a9 1a                        lda     #FN_ENTER_INITIALS ;enter initials
7628: 85 91                        sta     func_index
762a: 60                           rts

762b: 88           :NextEntry      dey                       ;move to next high score slot
762c: 88                           dey
762d: 88                           dey
762e: 10 b6                        bpl     :Loop             ;loop until all slots checked, then fall through
                   ; 
                   ; Cleans up after initials have been input for all eligible players.  Sets the
                   ; next function to show the high score table.
                   ; 
7630: 20 80 69     InitialsDone    jsr     ClearScreenPartial ;erase top part of screen
7633: a9 00                        lda     #$00
7635: 85 b9                        sta     cur_plyr_num      ;set player number to 1
7637: a5 fc                        lda     cocktail_flip
7639: 29 80                        and     #%10000000        ;clear flip bits (but keep cocktail-mode bit)
763b: 85 fc                        sta     cocktail_flip
763d: a9 1c                        lda     #FN_SHOW_HS       ;show the high score table
763f: 85 91                        sta     func_index
7641: 60                           rts

                   ; 
                   ; Horizontal positions for letters when entering initials.  We start with #2 and
                   ; count down.
                   ; 
7642: 82           initial_pos_tbl .dd1    130
7643: 78                           .dd1    120
7644: 6e                           .dd1    110

                   ; 
                   ; Func $1a: enter initials.
                   ; 
                   ; We use a couple of locations in the crosshair backing store to hold state: $0f
                   ; has the timeout counter, and $16 has the index into the high score initials
                   ; table.
                   ; 
                   ; The initial we're entering (0-2) is kept in the crosshair Y position.  The
                   ; current letter (A-Z or ' ') is held in the crosshair X position.
                   ; 
                   ; Trackball movement in both vertical and horizontal directions will cause the
                   ; letter to change.
                   ; 
                   • Clear variables
                   ]timeout        .var    $0f    {addr/1}
                   ]entry_index3   .var    $16    {addr/1}
                   ]horz_posn      .var    $1d    {addr/1}
                   ]vert_posn      .var    $1e    {addr/1}

7645: a5 f1        F_EnterInitials lda     in0_value         ;get button state
7647: 49 ff                        eor     #$ff              ;invert bits so 1=pressed
7649: 29 18                        and     #%00011000        ;check the p1/p2 start buttons
764b: d0 e3                        bne     InitialsDone      ;start button pressed, abandon entry
                   ; 
764d: a5 ca                        lda     game_frame_ctr    ;get frame counter
764f: 29 0f                        and     #%00001111        ;mask off high nibble
7651: d0 07                        bne     :GetInitials      ;branch unless low nibble is zero (every 16 frames)
7653: c6 0f                        dec     ]timeout          ;decrement timer
7655: d0 03                        bne     :GetInitials      ;haven't timed out, branch
7657: 4c 30 76                     jmp     InitialsDone      ;give up

765a: a2 00        :GetInitials    ldx     #$00
765c: a5 94                        lda     trk_h_delta       ;get horizontal move
765e: 86 94                        stx     trk_h_delta       ;reset delta
7660: 18                           clc
7661: 65 95                        adc     trk_v_delta       ;add to vertical move
7663: 86 95                        stx     trk_v_delta       ;reset delta
7665: 0a                           asl     A                 ;multiply by 4
7666: 0a                           asl     A
7667: 10 02                        bpl     :MovePos          ;branch if positive move
7669: c6 a5                        dec     cross_xc          ;decrement int part for negative move
766b: 18           :MovePos        clc
766c: 65 a3                        adc     cross_frac        ;add fractional part
766e: 85 a3                        sta     cross_frac
7670: a9 00                        lda     #$00
7672: 65 a5                        adc     cross_xc          ;add carry to int part
7674: 10 05                        bpl     :TestMag          ;branch if >= 0
7676: a9 1a                        lda     #26               ;set to ' ' (follows 'Z')
7678: b8                           clv
7679: 50 06                        bvc     :GotLetter        ;(always)

767b: c9 1b        :TestMag        cmp     #27               ;is it < ' ' that follows 'Z'?
767d: 90 02                        bcc     :GotLetter        ;yes, branch
767f: a9 00                        lda     #$00              ;set to 'A'
7681: 85 a5        :GotLetter      sta     cross_xc          ;update letter
                   ; 
7683: a4 0a                        ldy     last_cross_yc     ;get initial number (0-2)
7685: b9 42 76                     lda     initial_pos_tbl,y ;get horizontal position
7688: 85 1d                        sta     ]horz_posn        ;set ZP
768a: a9 b0                        lda     #176
768c: 85 1e                        sta     ]vert_posn        ;set vertical position
768e: a5 a5                        lda     cross_xc          ;get current letter (0-26)
7690: 18                           clc
7691: 69 41                        adc     #‘A’              ;add 'A' to make it ASCII (space is $5b)
7693: 20 ba 76                     jsr     PrintPaddedChar   ;print char
7696: a5 fa                        lda     fire_buttons      ;check the fire buttons
7698: 29 07                        and     #%00000111        ;are any of them pressed?
769a: f0 1d                        beq     :Return           ;no, bail
                   ; 
769c: a5 a5                        lda     cross_xc          ;get current letter
769e: 18                           clc
769f: 69 41                        adc     #‘A’              ;add 'A' to make it ASCII
76a1: a4 16                        ldy     ]entry_index3     ;get index into high score initials table
76a3: 99 2c 00                     sta     hscore_initials,y ;set value
76a6: e6 16                        inc     ]entry_index3     ;advance to next entry
                   ; 
76a8: a9 84                        lda     #132              ;132 * 16 = 2112 = ~35 sec
76aa: 85 0f                        sta     ]timeout          ;reset timeout
76ac: a2 00                        ldx     #$00
76ae: 86 fa                        stx     fire_buttons      ;reset button inputs
76b0: 86 a5                        stx     cross_xc          ;reset to 'A'
76b2: c6 0a                        dec     last_cross_yc     ;have we done all 3 initials?
76b4: 10 03                        bpl     :Return           ;not yet, bail
76b6: 20 e4 75                     jsr     PrepEnterInitials ;yes, clear state, init for next player
76b9: 60           :Return         rts

                   ; 
                   ; Prints a character with a little extra padding.  Used for high-score initials
                   ; and a couple of self-test features.
                   ; 
                   ; On entry:
                   ;   A-reg: ASCII value of character to print
                   ;   $1d: X coordinate of top-left corner
                   ;   $1e: Y coordinate of top-left corner
                   ; 
                   ; On exit:
                   ;   $1d: increased by 10 (one 8x8 cell + 2 pixels)
                   ;   $08: preserve background flag
                   ;   $0b: foreground color index (%PPP00000)
                   ;   $0c: background color index (%PPP00000)
                   ;   (X/Y-reg preserved)
                   ; 
                   • Clear variables
                   ]xc             .var    $00    {addr/1}
                   ]yc             .var    $01    {addr/1}
                   ]horz_posn      .var    $1d    {addr/1}
                   ]vert_posn      .var    $1e    {addr/1}
                   ]saved_x        .var    $23    {addr/1}
                   ]saved_y        .var    $24    {addr/1}

76ba: 84 24        PrintPaddedChar sty     ]saved_y          ;preserve X/Y-reg
76bc: 86 23                        stx     ]saved_x
76be: 20 9d 69                     jsr     GetGlyphPtr       ;get pointer to glyph in $0d-0e
76c1: a5 1d                        lda     ]horz_posn        ;set up X/Y position
76c3: 85 00                        sta     ]xc
76c5: a5 1e                        lda     ]vert_posn
76c7: 85 01                        sta     ]yc
76c9: a9 00                        lda     #$00              ;disable background preservation
76cb: 85 08                        sta     preserve_bg_flag
76cd: a9 40                        lda     #%01000000        ;fg color = 2
76cf: 85 0b                        sta     draw_fg_color
76d1: a9 00                        lda     #$00              ;bg color = 0
76d3: 85 0c                        sta     draw_bg_color
76d5: 20 27 66                     jsr     DrawGlyphPtrNoSc  ;draw glyph
                   ; 
76d8: a5 1d                        lda     ]horz_posn        ;move horizontal posn right one char + 2 pixels
76da: 18                           clc
76db: 69 0a                        adc     #10
76dd: 85 1d                        sta     ]horz_posn
76df: a6 23                        ldx     ]saved_x          ;restore X/Y-reg
76e1: a4 24                        ldy     ]saved_y
76e3: 60                           rts

                   ; 
                   ; Ensures that there are no vestigial pending high scores.
                   ; 
                   ; When one or both players get on the high score list, the first byte of the
                   ; initials for the entry is replaced with the player number (0 or 1).  This is
                   ; overwritten when the initials are entered.  This function ensures that we
                   ; don't have any pending entries left over, replacing the initials with an
                   ; invalid char that will be drawn as a blank.
                   ; 
76e4: a0 15        ScrubHighScores ldy     #21               ;7th entry, 3 bytes per entry
76e6: b9 2c 00     :Loop           lda     hscore_initials,y ;get first initial byte
76e9: f0 0f                        beq     :Valid            ;zero is okay
76eb: c9 03                        cmp     #$03
76ed: b0 0b                        bcs     :Valid            ;>= 3 is okay
76ef: a9 5b                        lda     #$5b              ;value was 0 or 1 (player number); scrub
76f1: 99 2c 00                     sta     hscore_initials,y ; unfinished entry
76f4: 99 2d 00                     sta     hscore_initials+1,y
76f7: 99 2e 00                     sta     hscore_initials+2,y
76fa: 88           :Valid          dey
76fb: 88                           dey
76fc: 88                           dey
76fd: 10 e7                        bpl     :Loop             ;loop until all entries checked
76ff: 60                           rts

                   ; 
                   ; Func $1c: show high score list.
                   ; 
                   • Clear variables
                   ]horz_out       .var    $1d    {addr/1}
                   ]vert_out       .var    $1e    {addr/1}
                   ]vert_posn      .var    $20    {addr/1}

                   F_ShowHighScores
7700: 20 80 69                     jsr     ClearScreenPartial ;erase top part of screen
7703: a9 00                        lda     #$00
7705: 85 b9                        sta     cur_plyr_num      ;set player 1
7707: 85 93                        sta     play_mode_flag    ;clear the game-in-progress flag
7709: 85 ae                        sta     num_players
770b: a5 fc                        lda     cocktail_flip
770d: 29 80                        and     #%10000000        ;clear flip bit (but keep cocktail-mode bit)
770f: 85 fc                        sta     cocktail_flip
7711: 20 23 75                     jsr     PrintTopScores    ;print player scores and highest score
7714: a9 01                        lda     #$01
7716: 20 51 6a                     jsr     PrintMessage      ;"HIGH SCORES"
7719: a9 c0                        lda     #192
771b: 85 20                        sta     ]vert_posn
771d: 20 21 69                     jsr     SetColorPalette   ;set palette for current wave (redundant?)
7720: 20 e4 76                     jsr     ScrubHighScores   ;purge vestigial pending scores
7723: a0 15                        ldy     #$15
7725: b9 46 00     :PrintScoreLoop lda     hscore_scores+2,y ;see if score in table was 000000
7728: 19 45 00                     ora     hscore_scores+1,y ;(indicates end of list reached)
772b: 19 44 00                     ora     hscore_scores,y
772e: d0 04                        bne     :ValidScore       ;score is nonzero, branch
7730: a8                           tay                       ;set Y-reg=0 so loop exits
7731: b8                           clv
7732: 50 3d                        bvc     :NextScore        ;(always)

7734: b9 46 00     :ValidScore     lda     hscore_scores+2,y ;copy score to BCD accumulator
7737: 85 d3                        sta     bcd3_acc+2
7739: b9 45 00                     lda     hscore_scores+1,y
773c: 85 d2                        sta     bcd3_acc+1
773e: b9 44 00                     lda     hscore_scores,y
7741: 85 d1                        sta     bcd3_acc
7743: 98                           tya                       ;preserve Y-reg
7744: 48                           pha
7745: a9 40                        lda     #$40
7747: a2 80                        ldx     #128              ;mid-screen
7749: a4 20                        ldy     ]vert_posn        ;vert posn
774b: 20 f6 6a                     jsr     PrintBcdNumber    ;print the number
                   ; 
774e: 68                           pla                       ;restore Y-reg
774f: a8                           tay
7750: a9 50                        lda     #80               ;horizontal position
7752: 85 1d                        sta     ]horz_out
7754: a5 20                        lda     ]vert_posn
7756: 85 1e                        sta     ]vert_out
7758: b9 2c 00                     lda     hscore_initials,y ;get first letter
775b: 20 ba 76                     jsr     PrintPaddedChar   ;print it (moves right one char)
775e: b9 2d 00                     lda     hscore_initials+1,y ;repeat for second letter
7761: 20 ba 76                     jsr     PrintPaddedChar
7764: b9 2e 00                     lda     hscore_initials+2,y ;and third letter
7767: 20 ba 76                     jsr     PrintPaddedChar
                   ; 
776a: a5 20                        lda     ]vert_posn        ;shift vertical position down 10 lines
776c: 38                           sec
776d: e9 0a                        sbc     #10
776f: 85 20                        sta     ]vert_posn
7771: 88           :NextScore      dey                       ;move to next entry
7772: 88                           dey
7773: 88                           dey
7774: 10 af                        bpl     :PrintScoreLoop   ;loop until done
                   ; 
7776: 20 aa 74                     jsr     PrintBonusCityPts ;show bonus city setting
7779: a9 14                        lda     #FN_SHOW_TITLE
777b: 85 92                        sta     next_func_index   ;show the title screen...
777d: a9 22                        lda     #FN_PAUSE
777f: 85 91                        sta     func_index        ;...after a brief delay
7781: a9 ff                        lda     #$ff
7783: 85 af                        sta     frame4_delay_ctr  ;wait for 255*4 frames (~17 sec)
7785: 60                           rts

                   ; 
                   ; Func $1e: player has deposited first of 2 coins.
                   ; 
                   ; Invoked when machine is in "2 coins 1 play" mode.  We put a message up on the
                   ; screen reminding them to insert another.
                   ; 
7786: 20 33 6c     F_HalfCredit    jsr     TxscResetStream   ;reset scrolling text
7789: 20 2f 67                     jsr     InitLangAndFlip   ;reset language and orientation
778c: 20 86 69                     jsr     ClearScreen       ;clear screen
778f: a9 01                        lda     #$01
7791: 85 a7                        sta     wave_num          ;set wave number to 1
7793: 20 21 69                     jsr     SetColorPalette   ;set colors for wave 1
7796: a9 0c                        lda     #$0c
7798: 20 51 6a                     jsr     PrintMessage      ;"2 COINS 1 PLAY"
779b: a9 20                        lda     #FN_WAIT_FULL_CREDIT ;switch to next function to wait for coin
779d: 85 91                        sta     func_index
                   ; 
                   ; Func $20: wait for second of two coins to drop.
                   ; 
                   F_WaitFullCredit
779f: a5 66                        lda     credit_count      ;do they have any credits?
77a1: f0 04                        beq     :Return           ;not yet, bail
77a3: a9 14                        lda     #FN_SHOW_TITLE
77a5: 85 91                        sta     func_index        ;coin dropped, switch to the title screen
77a7: 60           :Return         rts

                   ; 
                   ; Draws a smartbomb, by plotting individual points.  Smartbombs are symmetric
                   ; and don't need to be flipped when the screen is rotated.
                   ; 
                   ; On entry:
                   ;   $97: slot index of smart bomb (8-15)
                   ; 
                   • Clear variables
                   ]color_mask     .var    $98    {addr/1}

77a8: a9 ff        DrawSmartBomb   lda     #$ff              ;pass all color bits
77aa: d0 02                        bne     :DoDraw

77ac: a9 00        EraseSmartBomb  lda     #$00              ;clear all color bits (sets color = 0)
77ae: 85 98        :DoDraw         sta     ]color_mask
77b0: a0 0c                        ldy     #12               ;13 entries
77b2: a6 97        :Loop           ldx     slot_index
77b4: bd 5d 01                     lda     abm_cur_y,x       ;get smart bomb center Y
77b7: 18                           clc
77b8: 79 e2 77                     adc     :y_offset_tbl,y   ;add offset from table
77bb: 49 ff                        eor     #$ff              ;invert bits to form high byte of pointer
77bd: 85 07                        sta     gfx_ptr+1
77bf: bd 28 01                     lda     abm_cur_x,x       ;get smart bomb center X
77c2: 18                           clc
77c3: 79 d5 77                     adc     :x_offset_tbl,y   ;add offset from table
77c6: 85 06                        sta     gfx_ptr           ;use as low byte of pointer
                   ; 
77c8: b9 ef 77                     lda     :color_tbl,y      ;get color value from table
77cb: a2 00                        ldx     #$00
77cd: 25 98                        and     ]color_mask       ;clear bits if we're erasing
77cf: 81 06                        sta     (gfx_ptr,x)       ;set pixel
77d1: 88                           dey                       ;done with all pixels?
77d2: 10 de                        bpl     :Loop             ;no, loop
77d4: 60                           rts

                   ; 
                   ; Smartbomb image data.  13 sets of X/Y offset and color.  The color is
                   ; %PPP0000, and is either 2 or 4, with 0 in the middle.  (0 is background, 2 is
                   ; used for ICBM trails, 4 is cycled.)
                   ; 
                   ; Pattern:
                   ;     2
                   ;   2 4 2
                   ; 2 4 0 4 2
                   ;   2 4 2
                   ;     2
77d5: 00 ff 00 01+ :x_offset_tbl   .bulk   $00,$ff,$00,$01,$fe,$ff,$00,$01,$02,$ff,$00,$01,$00
77e2: 02 01 01 01+ :y_offset_tbl   .bulk   $02,$01,$01,$01,$00,$00,$00,$00,$00,$ff,$ff,$ff,$fe
77ef: 40 40 80 40+ :color_tbl      .bulk   $40,$40,$80,$40,$40,$80,$00,$80,$40,$40,$80,$40,$40

                   ; 
                   ; Handles coin slots and the slam sensor.  Executed during IRQ.
                   ; 
77fc: a2 02        CoinsAndCredits ldx     #$02              ;start with right coin slot
77fe: ad ed 00     :CoinLoop       lda:    in01_current      ;get most recent IN0 (w/o debounce)
7801: e0 01                        cpx     #$01              ; high 3 bits are coin R/C/L
7803: f0 03                        beq     :Shift2           ;X-reg=1, branch to shift 2x (center)
7805: b0 02                        bcs     :Shift1           ;X-reg=2, branch to shift 1x (right)
7807: 0a                           asl     A                 ;X-reg=0, shift 3x (left)
7808: 0a           :Shift2         asl     A                 ;shift the desired coin slot bit into the carry
7809: 0a           :Shift1         asl     A
780a: b5 5c                        lda     coin_timer1?,x    ;get coin timer
780c: 29 1f                        and     #%00011111        ;mod 32
780e: b0 37                        bcs     :SlotBitSet       ;if coin slot bit was set, branch
7810: f0 10                        beq     :SetValue1        ;if we're at zero, branch
7812: c9 1b                        cmp     #27               ;>= 27?
7814: b0 0a                        bcs     :Ge27             ;yes, branch
7816: a8                           tay                       ;preserve A-reg
7817: a5 fb                        lda     irq_counter       ;get the IRQ counter
7819: 29 07                        and     #$07
781b: c9 07                        cmp     #$07              ;set carry every 8 IRQs
781d: 98                           tya                       ;restore A-reg
781e: 90 02                        bcc     :SetValue1        ;branch most of the time
7820: e9 01        :Ge27           sbc     #$01              ;subtract one 1/8th of the time
7822: 95 5c        :SetValue1      sta     coin_timer1?,x    ;save value
                   ; Check slam switch.  If engaged, reset the coin state.
7824: ad ee 00                     lda:    in01_current+1    ;get most recent IN1 (w/o debounce)
7827: 29 20                        and     #%00100000        ;slam switch engaged?
7829: d0 04                        bne     :NoSlam           ;no, branch
782b: a9 f0                        lda     #$f0              ;yes, request an annoying noise
782d: 85 5f                        sta     slam_tone_ctr1
782f: a5 5f        :NoSlam         lda     slam_tone_ctr1    ;are we in tilt-whine mode?
7831: f0 08                        beq     :NoWhine          ;no, branch
7833: c6 5f                        dec     slam_tone_ctr1    ;count it down
7835: a9 00                        lda     #$00
7837: 95 5c                        sta     coin_timer1?,x    ;reset the coin stuff so we ignore anything that
7839: 95 60                        sta     coin_timer2?,x    ; was dropping
783b: 18           :NoWhine        clc                       ;clear carry, so branch to BCS always falls through
783c: b5 60                        lda     coin_timer2?,x    ;has value reached zero?
783e: f0 23                        beq     :MaybeNext        ;yes, move on to next slot
7840: d6 60                        dec     coin_timer2?,x    ;decrement
7842: d0 1f                        bne     :MaybeNext        ;not yet zero, move on to next slot
7844: 38                           sec                       ;set carry for next part
7845: b0 1c                        bcs     :MaybeNext        ;(always)

7847: c9 1b        :SlotBitSet     cmp     #27               ;fiddle with the timers
7849: b0 09                        bcs     :Ge27
784b: b5 5c                        lda     coin_timer1?,x
784d: 69 20                        adc     #32
784f: 90 d1                        bcc     :SetValue1
7851: f0 01                        beq     :Ge27
7853: 18                           clc
7854: a9 1f        :Ge27           lda     #31
7856: b0 ca                        bcs     :SetValue1
7858: 95 5c                        sta     coin_timer1?,x
785a: b5 60                        lda     coin_timer2?,x
785c: f0 01                        beq     :T20
785e: 38                           sec
785f: a9 78        :T20            lda     #120
7861: 95 60                        sta     coin_timer2?,x
7863: b0 03        :MaybeNext      bcs     :GotCoin
7865: 4c a3 78                     jmp     :Next

7868: a9 00        :GotCoin        lda     #$00              ;default slot config value
786a: e0 01                        cpx     #$01              ;left slot?
786c: 90 16                        bcc     :AddCred          ;yes, no config for left; branch
786e: f0 0c                        beq     :Center           ;branch if center slot
7870: a5 f3                        lda     r10_irq_mod       ;get R10 switches
7872: 29 0c                        and     #%00001100        ;mask right coin slot multiplier (x1/x4/x5/x6)
7874: 4a                           lsr     A                 ;shift right to get 0-3
7875: 4a                           lsr     A                 ;also clears carry flag
7876: f0 0c                        beq     :AddCred          ;if zero, branch
7878: 69 02                        adc     #$02              ;change 1-3 to 3-5
787a: d0 08                        bne     :AddCred          ;(always)

787c: a5 f3        :Center         lda     r10_irq_mod       ;get R10 switches
787e: 29 10                        and     #%00010000        ;mask center coin slot multiplier (x1/x2)
7880: f0 02                        beq     :AddCred          ;if zero, branch
7882: a9 01                        lda     #$01              ;set to 1
                   ; Coin slot multiplier - 1 now in A-reg.
7884: 38           :AddCred        sec                       ;set carry to add 1
7885: a8                           tay                       ;copy multiplier to Y-reg
7886: 65 67                        adc     coins_inserted    ;add to previously-inserted coins
7888: 85 67                        sta     coins_inserted
                   ; 
788a: a5 f4                        lda     r8_irq_inv        ;get R8 switches
788c: 29 04                        and     #%00000100        ;check "bonus credit for 4 quarters" setting
788e: f0 11                        beq     :No5for4          ;not set, branch
7890: 98                           tya                       ;get multiplier
7891: 38                           sec                       ;add one
7892: 65 68                        adc     recent_coins      ;add to coins inserted since last game
7894: c9 04        :BonusLoop      cmp     #$04              ;have at least 4 gone in?
7896: 90 07                        bcc     :SetCoins         ;no, branch
7898: 38                           sec
7899: e9 04                        sbc     #$04              ;subtract 4 from recent count
789b: e6 66                        inc     credit_count      ;increment the number of credits
789d: d0 f5                        bne     :BonusLoop        ;branch always, unless they deposit 256*4 coins

789f: 85 68        :SetCoins       sta     recent_coins      ;update recent coin count
78a1: f6 63        :No5for4        inc     coin_drop_ctr,x   ;incr count for this slot
78a3: ca           :Next           dex                       ;move to next coin slot
78a4: 30 03                        bmi     :AddCredits       ;all done, branch
78a6: 4c fe 77                     jmp     :CoinLoop         ;not done, loop

78a9: a5 f3        :AddCredits     lda     r10_irq_mod       ;get R10 switches; note IRQ does EOR %00000010
78ab: 29 03                        and     #%00000011        ;get coins-per-play setting
78ad: a8                           tay                       ;save copy in Y-reg
78ae: f0 12                        beq     :UpdateCoins      ;branch if zero (free play), with zero in A-reg
                   ; The modified coins-per-play setting is:
                   ;   0 - free play (handled above)
                   ;   1 - 1 coin 2 plays
                   ;   2 - 1 coin 1 play
                   ;   3 - 2 coins 1 play
                   ; By shifting right and adding the carry back in, we get 1 (1 coin 2 plays), 1
                   ; (1 coin 1 play), or 2 (2 coins 1 play), which is the number of coins needed to
                   ; get at least one credit.  We subtract that from the number of coins inserted
                   ; by negating and adding it.
78b0: 4a                           lsr     A                 ;shift low bit into carry
78b1: 69 00                        adc     #$00              ;add carry back if set
78b3: 49 ff                        eor     #$ff              ;negate value by inverting bits
78b5: 38                           sec                       ; and adding one
78b6: 65 67                        adc     coins_inserted    ;add negated value to coins inserted
78b8: 90 0a                        bcc     :UpdateCounters   ;not enough coins, branch
78ba: c0 02                        cpy     #$02              ;are we in 1 coin 2 plays mode?
78bc: b0 02                        bcs     :OneCredit        ;no, branch
78be: e6 66                        inc     credit_count      ;increment credits
78c0: e6 66        :OneCredit      inc     credit_count      ;increment credits
78c2: 85 67        :UpdateCoins    sta     coins_inserted    ;update coins inserted
                   ; 
78c4: a5 fb        :UpdateCounters lda     irq_counter       ;get IRQ counter
78c6: 4a                           lsr     A                 ;shift low bit into carry
78c7: b0 27                        bcs     :Return           ;bail every other time
                   ; Update the counter values.  If the counter is negative, the IRQ code writes a
                   ; bit to OUT0.
78c9: a0 00                        ldy     #$00              ;init mod count to zero
78cb: a2 02                        ldx     #$02              ;walk through all 3 coin slots
78cd: b5 63        :CtrLoop1       lda     coin_drop_ctr,x   ;get count
78cf: f0 09                        beq     :NextCtr1         ;if zero, branch
78d1: c9 10                        cmp     #$10              ;is it < 16?
78d3: 90 05                        bcc     :NextCtr1         ;yes, branch
78d5: 69 ef                        adc     #$ef              ;add -16 (note carry is set)
78d7: c8                           iny                       ;increment mod count
78d8: 95 63                        sta     coin_drop_ctr,x   ;store updated value
78da: ca           :NextCtr1       dex                       ;done with coin slots?
78db: 10 f0                        bpl     :CtrLoop1         ;no, branch
78dd: 98                           tya                       ;did we update a counter?
78de: d0 10                        bne     :Return           ;yes, bail
                   ; 
78e0: a2 02                        ldx     #$02              ;walk through all 3 coin slots
78e2: b5 63        :CtrLoop2       lda     coin_drop_ctr,x   ;get count
78e4: f0 07                        beq     :NextCtr2         ;if zero, branch
78e6: 18                           clc
78e7: 69 ef                        adc     #$ef              ;add -17
78e9: 95 63                        sta     coin_drop_ctr,x   ;store updated value
78eb: 30 03                        bmi     :Return           ;if it went negative, bail
78ed: ca           :NextCtr2       dex                       ;done with coin slots?
78ee: 10 f2                        bpl     :CtrLoop2         ;no, branch
78f0: 60           :Return         rts

                   ; 
                   ; Sound effect table offsets.  For each of the eight sound effects, these are
                   ; indices into the SFX audio data table for the start of the stream that drives
                   ; the POKEY sound channels.  Zero values indicate the register is not used by
                   ; this effect.
                   ; 
                   ; For example, effect #2 (explosion) uses two sound channels.  AUDF1 is at
                   ; offset +$01, AUDC1 is at offset +$07, AUDF2 is at offset +$0d, and AUDC2 is at
                   ; offset +$13.
                   ; 
78f1: 00 00 00 00+ sfx_indices     .bulk   $00,$00,$00,$00,$00,$00,$7d,$83 ;$01 silo is low
78f9: 01 07 0d 13+                 .bulk   $01,$07,$0d,$13,$00,$00,$00,$00 ;$02 explosion
7901: 00 00 00 00+                 .bulk   $00,$00,$00,$00,$19,$33,$00,$00 ;$04 ABM launched
7909: 41 47 00 00+                 .bulk   $41,$47,$00,$00,$00,$00,$00,$00 ;$08 bonus points
7911: 5d 77 00 00+                 .bulk   $5d,$77,$00,$00,$00,$00,$00,$00 ;$10 start of wave
7919: 91 97 9d a3+                 .bulk   $91,$97,$9d,$a3,$00,$00,$00,$00 ;$20 game over
7921: 4d 53 00 00+                 .bulk   $4d,$53,$00,$00,$00,$00,$00,$00 ;$40 bonus city used
7929: 00 00 00 00+                 .bulk   $00,$00,$00,$00,$00,$00,$a9,$b3 ;$80 unable to fire
                   ; 
                   ; POKEY audio has four channels, with two 8-bit I/O locations per channel (AUDFn
                   ; and AUDCn).  All four channels are used.
                   ; 
                   ; The AUDFn setting determines frequency.  Larger value == lower pitch.
                   ; 
                   ; The AUDCn value is NNNFVVVV, where N is a noise / distortion setting, F is
                   ; "forced volume-only output" enable, and V is the volume level.
                   ; 
                   ; In the table below, each chunk has 4 values:
                   ;  +00 initial value
                   ;  +01 duration
                   ;  +03 increment
                   ;  +04 repetition count
                   ; 
                   ; The sound specified by the value is played until the duration reaches zero. 
                   ; If the repetition count is nonzero, the value is increased or decreased by the
                   ; increment, and the duration is reset.  When the repetition count reaches zero,
                   ; the next chunk is loaded.  If the chunk has the value $00, the sequence ends. 
                   ; The counters are updated by the ~240Hz NMI.
                   ; 
                   ; Because AUDFn and AUDCn are specified by different chunks, care must be taken
                   ; to ensure the durations run out at the same time, i.e. (duration * rep_count)
                   ; is equal.
                   ; 
                   ; (FWIW, this is the same approach that Battlezone uses.)
                   ; 
7931: 00           sfx_audio_data  .dd1    $00
7932: a0 10 04 10                  .bulk   $a0,$10,$04,$10   ;+$01: sound $02 F1 (explosion)
7936: 00 00                        .bulk   $00,$00
7938: 86 40 fe 04                  .bulk   $86,$40,$fe,$04   ;+$07: sound $02 C1
793c: 00 00                        .bulk   $00,$00
793e: c0 10 04 10                  .bulk   $c0,$10,$04,$10   ;+$0d: sound $02 F2 (explosion)
7942: 00 00                        .bulk   $00,$00
7944: 86 40 fe 04                  .bulk   $86,$40,$fe,$04   ;+$13: sound $02 C2
7948: 00 00                        .bulk   $00,$00
794a: 60 10 00 01                  .bulk   $60,$10,$00,$01   ;+$19: sound $04 F3 (ABM launch)
794e: 50 10 f8 01                  .bulk   $50,$10,$f8,$01
7952: 48 10 18 01                  .bulk   $48,$10,$18,$01
7956: 60 18 f0 01                  .bulk   $60,$18,$f0,$01
795a: 60 10 f8 01                  .bulk   $60,$10,$f8,$01
795e: 60 10 08 10                  .bulk   $60,$10,$08,$10
7962: 00 00                        .bulk   $00,$00
7964: 82 20 02 01                  .bulk   $82,$20,$02,$01   ;+$33: sound $04 C3
7968: 84 10 00 04                  .bulk   $84,$10,$00,$04
796c: 84 38 ff 04                  .bulk   $84,$38,$ff,$04
7970: 00 00                        .bulk   $00,$00
7972: 10 04 00 01                  .bulk   $10,$04,$00,$01   ;+$41: sound $08 F1 (bonus points)
7976: 00 00                        .bulk   $00,$00
7978: 2f 04 0f 01                  .bulk   $2f,$04,$0f,$01   ;+$47: sound $08 C1
797c: 00 00                        .bulk   $00,$00
797e: 18 18 00 18                  .bulk   $18,$18,$00,$18   ;+$4d: sound $40 F1 (bonus city; randomized)
7982: 00 00                        .bulk   $00,$00
7984: a4 18 00 18                  .bulk   $a4,$18,$00,$18   ;+$53: sound $40 C1
7988: a0 10 00 02                  .bulk   $a0,$10,$00,$02
798c: 00 00                        .bulk   $00,$00
798e: 10 02 01 20                  .bulk   $10,$02,$01,$20   ;+$5d: sound $10 F1 (wave start 6 whoops)
7992: 10 02 01 20                  .bulk   $10,$02,$01,$20
7996: 10 02 01 20                  .bulk   $10,$02,$01,$20
799a: 10 02 01 20                  .bulk   $10,$02,$01,$20
799e: 10 02 01 20                  .bulk   $10,$02,$01,$20
79a2: 10 02 01 20                  .bulk   $10,$02,$01,$20
79a6: 00 00                        .bulk   $00,$00
79a8: a4 02 00 c0                  .bulk   $a4,$02,$00,$c0   ;+$77: sound $10 C1
79ac: 00 00                        .bulk   $00,$00
79ae: 20 80 00 03                  .bulk   $20,$80,$00,$03   ;+$7d: sound $01 F4 (silo low)
79b2: 00 00                        .bulk   $00,$00
79b4: a3 40 fd 02                  .bulk   $a3,$40,$fd,$02   ;+$83: sound $01 C4
79b8: a3 40 fd 02                  .bulk   $a3,$40,$fd,$02
79bc: a3 40 fd 02                  .bulk   $a3,$40,$fd,$02
79c0: 00 00                        .bulk   $00,$00
79c2: 40 ff 02 08                  .bulk   $40,$ff,$02,$08   ;+$91: sound $20 F1 (game over)
79c6: 00 00                        .bulk   $00,$00           ; duration = 255*8/240Hz = ~8.5 sec
79c8: 88 ff ff 08                  .bulk   $88,$ff,$ff,$08   ;+$97: sound $20 C1
79cc: 00 00                        .bulk   $00,$00
79ce: c0 ff 02 08                  .bulk   $c0,$ff,$02,$08   ;+$9d: sound $20 F2
79d2: 00 00                        .bulk   $00,$00
79d4: 88 ff ff 08                  .bulk   $88,$ff,$ff,$08   ;+$a3: sound $20 C2
79d8: 00 00                        .bulk   $00,$00
79da: 18 02 ff 10                  .bulk   $18,$02,$ff,$10   ;+$a9: sound $80 F4 (unable to fire)
79de: 08 20 00 01                  .bulk   $08,$20,$00,$01
79e2: 00 00                        .bulk   $00,$00
79e4: a4 10 ff 04                  .bulk   $a4,$10,$ff,$04   ;+$b3: sound $80 C4
79e8: 00 00                        .bulk   $00,$00

                   ; 
                   ; Plays sound $02 (explosion).
                   ; 
79ea: a9 02        PlaySound02     lda     #SFX_EXPLOSION    ;fall through
                   ; 
                   ; Starts a fire-and-forget sound effect.
                   ; 
                   ; The noise made by fliers and smartbombs is handled elsewhere.
                   ; 
                   ; Sound values are passed as a single-bit value:
                   ;   $01: silo is low ("doot doot doot")
                   ;   $02: explosion (low rumble)
                   ;   $04: ABM launched ("whoosh")
                   ;   $08: bonus points awarded at end of wave ("thup")
                   ;   $10: start of wave (six sirens)
                   ;   $20: game over (extended low explosion)
                   ;   $40: bonus city used (several short random tones)
                   ;   $80: unable to fire ("dwing")
                   ; 
                   ; Only the first (highest) set bit is handled; you cannot start multiple sound
                   ; effects with one call.  $00 and $01 are equivalent.
                   ; 
                   ; There are four audio channels in the POKEY, each of which is controlled by two
                   ; bytes: the frequency, and the control (volume/noise).  Sounds here configure
                   ; one or two of the channels.
                   ; 
                   ; For some reason this uses absolute addressing to reference certain zero-page
                   ; values.
                   ; 
                   ; On entry:
                   ;   A-reg: sound index
                   ;   (X/Y-reg preserved)
                   ; 
79ec: 2c 93 00     StartSfx        bit:    play_mode_flag    ;is a game in progress?
79ef: 10 3a                        bpl     :Return           ;no, bail (be silent in attract mode)
79f1: 8d 02 00                     sta:    tmp_02            ;save sound value
79f4: 85 69                        sta     last_sound_started ;save for IRQ code, which checks bit 6 (V)
79f6: 8e 98 00                     stx:    tmp_98            ;preserve X-reg
79f9: 8c 99 00                     sty:    tmp_99            ;preserve Y-reg
79fc: aa                           tax                       ;set flags for A-reg
79fd: f0 08                        beq     :SoundZero        ;if zero, branch with X-reg=0
79ff: a2 08                        ldx     #$08              ;shift through all 8 bits
7a01: 0e 02 00     :BitLoop        asl:    tmp_02            ;shift the high bit into the carry
7a04: ca                           dex                       ;decrement the counter
7a05: 90 fa                        bcc     :BitLoop          ;if bit wasn't set, loop
                   ; 
7a07: 8a           :SoundZero      txa                       ;put bit number (0-7) in A-reg
7a08: 0a                           asl     A                 ;multiply by 8 (0-56); clears carry
7a09: 0a                           asl     A
7a0a: 0a                           asl     A
7a0b: 69 07                        adc     #$07              ;add 7 to get offset of last byte
7a0d: a8                           tay                       ;use as index
7a0e: c0 47                        cpy     #71               ;>= 71? (impossible?)
7a10: b0 13                        bcs     :XYReturn         ;yes, bail
                   ; 
7a12: a2 07                        ldx     #$07              ;copy up to 8 bytes
7a14: b9 f1 78     :Loop           lda     sfx_indices,y     ;get initial index
7a17: f0 08                        beq     :Skip             ;if zero, don't overwite this entry
7a19: 95 6a                        sta     audio_indices,x   ;copy the index
7a1b: a9 01                        lda     #$01
7a1d: 95 7a                        sta     audio_dur_ctr,x   ;set counters to 1
7a1f: 95 82                        sta     audio_rep_ctr,x
7a21: 88           :Skip           dey                       ;move to next entry
7a22: ca                           dex
7a23: 10 ef                        bpl     :Loop             ;loop until done
                   ; 
7a25: ae 98 00     :XYReturn       ldx:    tmp_98            ;restore X-reg
7a28: ac 99 00                     ldy:    tmp_99            ;restore Y-reg
7a2b: 60           :Return         rts

                   ; 
                   ; Makes an unpleasant noise.  Executed during IRQ.
                   ; 
7a2c: a9 18        MakeSlamSound   lda     #$18              ;frequency (fairly high pitch)
7a2e: a0 af                        ldy     #%10101111        ;max volume, no poly (pure tone)
7a30: 8d 00 40                     sta     POKEY_AUDF1       ;set channel 1
7a33: 8c 01 40                     sty     POKEY_AUDC1
7a36: 60                           rts

                   ; 
                   ; Updates sound effect generation.  Executed during IRQ.
                   ; 
7a37: ad e0 00     UpdateAudio     lda:    slam_tone_ctr2    ;are we complaining about a slam?
7a3a: d0 f0                        bne     MakeSlamSound     ;yes, do that and ignore the rest
                   ; 
7a3c: a2 07                        ldx     #$07
7a3e: d6 7a        :Loop           dec     audio_dur_ctr,x   ;decrement duration counter
7a40: d0 56                        bne     :SetAudio         ;hasn't expired; branch
7a42: b4 6a                        ldy     audio_indices,x   ;get index of next thing
7a44: f0 52                        beq     :SetAudio         ;end of list; play sound (next loop will silence it)
7a46: d6 82                        dec     audio_rep_ctr,x   ;decrement repeat counter
7a48: d0 30                        bne     :RepsNonZero      ;more reps to go, branch
7a4a: b9 31 79                     lda     sfx_audio_data,y  ;get the new value
7a4d: 95 72                        sta     audio_values,x
7a4f: b9 32 79                     lda     sfx_audio_data+1,y ;and the new count
7a52: 95 7a                        sta     audio_dur_ctr,x
7a54: d0 15                        bne     :MoreReps         ;if we're still going, branch
7a56: 95 6a                        sta     audio_indices,x   ;otherwise, zero out the index
7a58: 8a                           txa
7a59: d0 3d                        bne     :SetAudio         ;finish up
                   ; 
7a5b: a9 00                        lda     #$00              ;disable sound on this channel
7a5d: 95 72                        sta     audio_values,x
7a5f: 9d 00 40                     sta     POKEY_AUDF1,x     ;set frequency or volume to zero
7a62: a9 00                        lda     #$00
7a64: 95 6a                        sta     audio_indices,x   ;zero the index
7a66: ca                           dex                       ;done yet?
7a67: 10 d5                        bpl     :Loop             ;no, loop
7a69: 30 35                        bmi     :Engine           ;yes, go fiddle with engine sounds

7a6b: b9 34 79     :MoreReps       lda     sfx_audio_data+3,y ;get rep counter from index + 3
7a6e: 95 82                        sta     audio_rep_ctr,x
7a70: b5 6a                        lda     audio_indices,x   ;advance index by 4
7a72: 18                           clc
7a73: 69 04                        adc     #$04
7a75: 95 6a                        sta     audio_indices,x
7a77: 4c 98 7a                     jmp     :SetAudio         ;finish up

                   ; Duration hit zero, but more reps to go.
7a7a: b9 2e 79     :RepsNonZero    lda     sfx_audio_data-3,y ;reload previous value; need to reference backward
7a7d: 95 7a                        sta     audio_dur_ctr,x   ; because we already incremented index
7a7f: b5 72                        lda     audio_values,x
7a81: 18                           clc
7a82: 79 2f 79                     adc     sfx_audio_data-2,y
7a85: 24 69                        bit     last_sound_started ;is sound $40 active (bonus city used)?
7a87: 50 0d                        bvc     :SetThing?        ;no, branch
7a89: e0 01                        cpx     #$01              ;channel 1 ctl?
7a8b: f0 09                        beq     :SetThing?        ;yes, branch; only the freq is randomized, and
7a8d: ad 0a 40                     lda     POKEY_RANDOM      ; at this point all other channels are slient
7a90: 29 1e                        and     #%00011110        ;keep 4 bits (0-30, even only)
7a92: d0 02                        bne     :SetThing?        ;if not zero, keep it; branch
7a94: a9 1e                        lda     #30               ;set to 30
7a96: 95 72        :SetThing?      sta     audio_values,x    ;store value in POKEY list
                   ; 
7a98: b5 72        :SetAudio       lda     audio_values,x    ;copy value to POKEY
7a9a: 9d 00 40                     sta     POKEY_AUDF1,x
7a9d: ca                           dex                       ;done with all eight?
7a9e: 10 9e                        bpl     :Loop             ;not yet, loop
                   ; 
                   ; Handle engine sounds (fliers and bombs).
7aa0: a5 70        :Engine         lda     audio_indices+6   ;is channel 4 available?
7aa2: d0 26                        bne     :Return           ;no, bail
7aa4: a5 8a                        lda     engine_sfx_flags  ;flier or bomb on screen?
7aa6: f0 22                        beq     :Return           ;no, bail
7aa8: 2a                           rol     A                 ;shift into low bits
7aa9: 2a                           rol     A
7aaa: 2a                           rol     A                 ;%000000BF (bomb/flier)
7aab: 29 03                        and     #$03              ;remove excess (not needed?)
7aad: aa                           tax                       ;use as index (1-3)
7aae: a5 8b                        lda     engine_sfx_freq   ;get current frequency
7ab0: dd ca 7a                     cmp     :engine_freq_en-1,x ;have we reached the highest freq?
7ab3: d0 06                        bne     :UpdateFreq       ;no, branch
7ab5: bd cd 7a                     lda     :engine_freq_st-1,x ;reset the frequency
7ab8: b8                           clv
7ab9: 50 03                        bvc     :SetFreq          ;(always)

7abb: 38           :UpdateFreq     sec
7abc: e9 02                        sbc     #$02              ;reduce by 2
7abe: 8d 06 40     :SetFreq        sta     POKEY_AUDF4       ;update frequency in POKEY
7ac1: 85 8b                        sta     engine_sfx_freq   ;store updated value
7ac3: a9 a4                        lda     #%10100100        ;volume=4, noise=pure
7ac5: 8d 07 40                     sta     POKEY_AUDC4       ;make sure channel 4 is configured correctly
7ac8: 85 79                        sta     audio_values+7    ;update the POKEY list
7aca: 60           :Return         rts

                   ; 
                   ; Entries for: flier only, bomb only, bomb+flier.  Flier runs from $70 to $30,
                   ; bomb runs from $30 to $00, and when both are onscreen you only hear the bomb.
7acb: 30 00 00     :engine_freq_en .bulk   $30,$00,$00
7ace: 70 30 30     :engine_freq_st .bulk   $70,$30,$30

                   ; 
                   ; Enables continuous sound when smartbomb on screen.
                   ; 
7ad1: a9 30        EnableBombSound lda     #$30
7ad3: 85 8b                        sta     engine_sfx_freq   ;init frequency
7ad5: a5 8a                        lda     engine_sfx_flags  ;add bomb to flags
7ad7: 09 80                        ora     #%10000000
7ad9: 85 8a                        sta     engine_sfx_flags
7adb: 60                           rts

                   ; 
                   ; Enables continuous sound when flier on screen.
                   ; 
                   EnableFlierSound
7adc: a5 8a                        lda     engine_sfx_flags  ;add flier to flags
7ade: 09 40                        ora     #%01000000
7ae0: 85 8a                        sta     engine_sfx_flags
7ae2: 30 04                        bmi     :Return           ;branch if bomb is active
7ae4: a9 70                        lda     #$70              ;otherwise, init frequency
7ae6: 85 8b                        sta     engine_sfx_freq
7ae8: 60           :Return         rts

                   ; 
                   ; Disables smartbomb sound.
                   ; 
                   DisableBombSound
7ae9: ad da 00                     lda:    num_live_bombs    ;check number of live bombs
7aec: d0 1d                        bne     :Return           ;at least one still alive; bail
7aee: a5 8a                        lda     engine_sfx_flags  ;remove bomb from flags
7af0: 29 7f                        and     #%01111111
7af2: 85 8a                        sta     engine_sfx_flags
7af4: 24 8a                        bit     engine_sfx_flags  ;is the flier sfx enabled?
7af6: 70 e4                        bvs     EnableFlierSound  ;yes, update audio; otherwise fall through
                   ; 
                   ; Disables flier sound.
                   ; 
                   DisableFlierSound
7af8: a5 8a                        lda     engine_sfx_flags  ;remove flier from flags
7afa: 29 bf                        and     #%10111111
7afc: 85 8a                        sta     engine_sfx_flags
7afe: ad da 00                     lda:    num_live_bombs    ;check number of live bombs
7b01: d0 08                        bne     :Return           ;at least one bomb; bail
7b03: 85 8a                        sta     engine_sfx_flags  ;turn off engine sound (A-reg=0)
7b05: 8d 06 40                     sta     POKEY_AUDF4       ;silence audio channel 4
7b08: 8d 07 40                     sta     POKEY_AUDC4
7b0b: 60           :Return         rts

                   ; 
                   ; Initializes the POKEY, silencing all sound channels.
                   ; 
7b0c: a9 00        InitPokey       lda     #%00000000        ;disable everything
7b0e: 8d 0f 40                     sta     POKEY_SKCTL
7b11: a9 03                        lda     #%00000011        ;enable KB scan and debounce
7b13: 8d 0f 40                     sta     POKEY_SKCTL
                   ; 
7b16: a2 07                        ldx     #$07              ;write 8 locations
7b18: a9 00                        lda     #$00
7b1a: 9d 00 40     :Loop           sta     POKEY_AUDF1,x     ;zero freq and control for all 4 channels
7b1d: 95 6a                        sta     audio_indices,x   ;init audio controller
7b1f: 95 72                        sta     audio_values,x
7b21: ca                           dex
7b22: 10 f6                        bpl     :Loop
                   ; 
7b24: a9 20                        lda     #%00100000        ;bit 5: set channel 3 frequency 1.79 MHz
7b26: 8d 08 40                     sta     POKEY_AUDCTL
7b29: 60                           rts

                   ; 
                   ; Handles IRQ.
                   ; 
                   ; This fires every 4x per screen refresh, ~240Hz.  If it arrives during VBLANK,
                   ; we trigger the game code to execute after we finish.
                   ; 
                   ; Start by confirming that the system is in a good state.
                   ; 
7b2a: 48           HandleIRQ       pha                       ;preserve A/X/Y-reg
7b2b: 8a                           txa
7b2c: 48                           pha
7b2d: 98                           tya
7b2e: 48                           pha
7b2f: d8                           cld                       ;clear decimal mode (required on 6502)
7b30: ba                           tsx                       ;check stack level
7b31: e0 e0                        cpx     #$e0              ;dropped below $1e0?
7b33: 90 16                        bcc     HandleReset       ;yes, reset system
7b35: bd 04 01                     lda     stack_plus4,x     ;check saved P flags
7b38: 29 10                        and     #%00010000        ;check BRK bit
7b3a: d0 0f                        bne     HandleReset       ;if we hit a software break, fail
7b3c: bd 06 01                     lda     stack_plus6,x     ;check high byte of interrupted PC
7b3f: c9 50                        cmp     #$50              ;< $5000?
7b41: 90 08                        bcc     HandleReset       ;yes, fail
7b43: c9 80                        cmp     #$80              ;>= $8000?
7b45: b0 04                        bcs     HandleReset       ;yes, fail
7b47: a5 9f                        lda     exec_flag         ;check if main loop is running
7b49: 10 1f                        bpl     DoHandleIRQ       ;all is well, branch to IRQ implementation
                   ; 
                   ; Handles RESET signal.  Various things branch here if they panic.
                   ; 
7b4b: a2 ff        HandleReset     ldx     #$ff              ;init stack pointer to top
7b4d: 9a                           txs
7b4e: d8                           cld                       ;clear decimal mode, just in case
7b4f: e8                           inx                       ;X-reg=0
7b50: 8a                           txa                       ;A-reg=0
7b51: 78                           sei                       ;disable interrupts
7b52: 95 00        :ZeroLoop       sta     $00,x             ;zero out zero page
7b54: 9d 00 01                     sta     STACK,x           ;zero out stack
7b57: ca                           dex
7b58: d0 f8                        bne     :ZeroLoop
7b5a: a9 40                        lda     #%01000000        ;reset screen-flip bit, light LEDs
7b5c: 8d 00 48                     sta     OUT0
7b5f: 2c 00 49                     bit     IN1               ;check self test switch
7b62: 50 03                        bvc     :Jmp_SelfTest     ;bit 6 clear, do self test
7b64: 4c 01 50                     jmp     START             ;start game

7b67: 4c 82 7c     :Jmp_SelfTest   jmp     SelfTest

                   ; 
                   ; System seems fine, do the IRQ processing.
                   ; 
7b6a: 8d 00 4c     DoHandleIRQ     sta     WATCHDOG          ;feed the dog
7b6d: e6 fb                        inc     irq_counter       ;inc counter used for periodic events
7b6f: ad 00 49                     lda     IN1               ;check vblank flag
7b72: 10 34                        bpl     :NotVblank        ;not set, branch
                   ; 
                   ; We're in VBLANK, do some extra stuff.
                   ; 
7b74: e6 9f                        inc     exec_flag         ;tell non-interrupt code to run
7b76: a5 d8                        lda     indic_phase_counter ;update counter used to flash the "1UP" indicator
7b78: f0 02                        beq     :NoDec            ;don't decrement below zero
7b7a: c6 d8                        dec     indic_phase_counter
                   ; Copy color values, cycling if appropriate.  Non-IRQ code should set the mask
                   ; before setting values.
7b7c: a5 ec        :NoDec          lda     color_cyc_mask
7b7e: 85 f3                        sta     r10_irq_mod       ;save copy in ZP (uses $f3 as temporary)
7b80: a2 07                        ldx     #$07              ;check 8 entries
7b82: 06 f3        :ColorLoop      asl     r10_irq_mod       ;test first bit
7b84: 90 02                        bcc     :NoInc            ;not set, leave color alone
7b86: f6 e4                        inc     color_palette,x   ;increment the value
7b88: b5 e4        :NoInc          lda     color_palette,x
7b8a: 9d 00 4b                     sta     COLOR_PAL,x       ;copy color to display hardware
7b8d: ca                           dex                       ;done yet?
7b8e: 10 f2                        bpl     :ColorLoop        ;no, loop
                   ; Latch R10.  (Not sure why we only do this in VBLANK, though there's no value
                   ; in doing it more often.)
7b90: ad 00 4a                     lda     SWITCH_R10        ;get R10 dip switch values
7b93: 49 02                        eor     #%00000010        ;flip the sense of bit 1 (part of coins-per-play)
7b95: 85 f3                        sta     r10_irq_mod       ;save in ZP
                   ; Update OUT0 setting.  We want to set bit 6 to 1 for normal orientation, 0 for
                   ; flipped.  The decision hinges on whether player 2 is up, so we need to check
                   ; $fc (which inverts the meaning of bit 6, i.e. 1 is flipped).
7b97: a5 f5                        lda     out0_bits         ;get previous OUT0 value
7b99: 29 bf                        and     #%10111111        ;zero screen flip bit
7b9b: a8                           tay                       ;copy to Y-reg
7b9c: a5 fc                        lda     cocktail_flip     ;get current flip bits
7b9e: 29 3e                        and     #%00111110        ;check middle bits
7ba0: d0 04                        bne     :DoFlip           ;nonzero, we're cocktail+player2, so branch
7ba2: 98                           tya                       ;put previous OUT0 back in A-reg
7ba3: 09 40                        ora     #%01000000        ;set the not-flipped bit
7ba5: a8                           tay                       ;back to Y-reg
7ba6: 84 f5        :DoFlip         sty     out0_bits         ;set value to write to OUT0
                   ; Done with VBLANK-only stuff.
                   ; 
                   ; Read inputs.
7ba8: ae 00 49     :NotVblank      ldx     IN1               ;read current state of IN1
7bab: 86 ee                        stx     in01_current+1    ;save in ZP
                   ; 
7bad: a5 f5                        lda     out0_bits         ;get bits for OUT0
7baf: 29 fe                        and     #%11111110        ;clear low bit (CTRLD)
7bb1: a8                           tay                       ;save in Y-reg
7bb2: ae 00 48                     ldx     IN0               ;read current IN0 value (trackball)
7bb5: 86 f6                        stx     in0_trkball1      ;save in ZP
7bb7: 8c 00 48                     sty     OUT0              ;set CTRLD=0
7bba: ae 00 48                     ldx     IN0               ;read switches
7bbd: 86 ed                        stx     in01_current      ;save in ZP
7bbf: 09 01                        ora     #%00000001        ;set low bit (CTRLD)
7bc1: 8d 00 48                     sta     OUT0              ;set CTRLD=1
7bc4: ae 00 48                     ldx     IN0               ;read trackball again; this will be written to
7bc7: 86 f8                        stx     in0_trkball2      ; the "prev" value later
                   ; Capture trackball movement.  The value read from IN0 gives 4 bits of position
                   ; horizontally and vertically.  We subtract that from the previous position to
                   ; get a signed 4-bit movement delta, which we add to results from previous
                   ; frames.  The accumulated delta gets zeroed out by the main program ($5140),
                   ; but we run at least 4x as often.
7bc9: a5 f6                        lda     in0_trkball1      ;get movement values
7bcb: 38                           sec
7bcc: e5 f7                        sbc     in0_trk_prev      ;subtract previous value
7bce: 29 0f                        and     #%00001111        ;mod 32
7bd0: c9 08                        cmp     #$08              ;was value positive (0-7)? (roll right)
7bd2: 90 02                        bcc     :PosH             ;yes, branch
7bd4: 09 f0                        ora     #$f0              ;sign-extend negative
7bd6: 18           :PosH           clc
7bd7: 65 94                        adc     trk_h_delta       ;add to previous delta
7bd9: 85 94                        sta     trk_h_delta       ;store result
                   ; 
7bdb: a5 f6                        lda     in0_trkball1      ;get movement values
7bdd: 38                           sec
7bde: 09 0f                        ora     #$0f              ;fill low nibble with 1s to prevent borrow
7be0: e5 f7                        sbc     in0_trk_prev      ;subtract previous value
7be2: 4a                           lsr     A                 ;right-shift to get vertical displacement
7be3: 4a                           lsr     A                 ;also performs mod 32
7be4: 4a                           lsr     A
7be5: 4a                           lsr     A
7be6: c9 08                        cmp     #$08              ;was value positive (0-7)? (roll upward)
7be8: 90 02                        bcc     :PosV             ;yes, branch
7bea: 09 f0                        ora     #$f0              ;sign-extend negative
7bec: 18           :PosV           clc
7bed: 65 95                        adc     trk_v_delta       ;add to previous delta
7bef: 85 95                        sta     trk_v_delta       ;store result
                   ; 
7bf1: a5 f8                        lda     in0_trkball2      ;get second IN0 read
7bf3: 85 f7                        sta     in0_trk_prev      ;update previous-trackball value
                   ; Read R8 switches, which show up at the POKEY_ALLPOT address.
7bf5: a9 07                        lda     #$07
7bf7: 8d 0f 40                     sta     POKEY_SKCTL       ;enable pot scan and debounce
7bfa: 8d 0b 40                     sta     POKEY_POTGO       ;start potentiometer scan
7bfd: ad 08 40                     lda     SWITCH_R8         ;read R8 switches
7c00: 49 7f                        eor     #%01111111        ;invert bits, except bit 7 (unused?)
7c02: 85 f4                        sta     r8_irq_inv        ;save to ZP
                   ; 
7c04: 24 f2                        bit     in1_value         ;check debounced IN1
7c06: 50 06                        bvc     :DebounceIN01     ;self-test switch is enabled, skip the next couple things
7c08: 20 37 7a                     jsr     UpdateAudio       ;update audio output
7c0b: 20 fc 77                     jsr     CoinsAndCredits   ;manage the coin slots
                   ; Process IN0/IN1, with debouncing.
                   ; 
                   ; $ed/ee hold the "current" values from IN0 and IN1.  The low three bits of IN1
                   ; have the fire button state for player 1, while the low three bits of IN0 have
                   ; the fire button state for player 2.
                   ; 
                   ; $ef/f0 hold the values from the "previous" IRQ period.  These are overwritten
                   ; with the "current" values.
                   ; 
                   ; $f1/f2 holds a "merged" version, equal to:
                   ;   (current | previous) & ((current & previous) | merged)
                   ; 
                   ;  C P M -> P M
                   ;  0 0 0    0 0
                   ;  0 0 1    0 0
                   ;  0 1 0    0 0
                   ;  0 1 1    0 1
                   ;  1 0 0    1 0
                   ;  1 0 1    1 1
                   ;  1 1 0    1 1
                   ;  1 1 1    1 1
                   ; 
                   ; The "merged" value matches the "current" value unless current/previous don't
                   ; match, and current/merged don't match.  So a button state change that is only
                   ; visible for one IRQ period won't cause the merged value to change.
7c0e: a2 01        :DebounceIN01   ldx     #$01
7c10: b4 ed        :Loop           ldy     in01_current,x    ;Y-reg = in0 or in1 value
7c12: b5 ef                        lda     in01_prev,x       ;A-reg = previous value
7c14: 94 ef                        sty     in01_prev,x       ;store new value
7c16: a8                           tay                       ;put previous value in Y-reg
7c17: 35 ef                        and     in01_prev,x       ;AND new and previous values
7c19: 15 f1                        ora     in0_value,x       ;combine with f1
7c1b: 95 f1                        sta     in0_value,x
7c1d: 98                           tya                       ;put previous value in A-reg
7c1e: 15 ef                        ora     in01_prev,x
7c20: 35 f1                        and     in0_value,x
7c22: 95 f1                        sta     in0_value,x
7c24: ca                           dex
7c25: 10 e9                        bpl     :Loop
                   ; Read the fire buttons for the current player.  The goal is to set a 1 bit in
                   ; $fa when a button is pressed.  The main code will clear it to zero after it
                   ; has been read.  A cocktail cabinet has two sets of buttons, so if player 2 is
                   ; active we need to read the other set.
                   ; 
                   ; Input bits are 0 when the button is pressed.  We want to set bits in $fa when
                   ; a button is pressed, and hold them until the main code has a chance to latch
                   ; the value and reset $fa to zero.  We don't want to set the bits again until
                   ; after the button has been released.
                   ; 
                   ; The state machine looks like this:
                   ;   Idle:    IN1=111 f9=111 fa=000 -> fa=000 f9=111
                   ;   Press:   IN1=110 f9=111 fa=000 -> fa=001 f9=110
                   ;   Wait:    IN1=110 f9=110 fa=001 -> fa=001 f9=110
                   ;   Latch:   IN1=110 f9=110 fa=000 -> fa=000 f9=110
                   ;   Hold:    IN1=110 f9=110 fa=000 -> fa=000 f9=110
                   ;   Release: IN1=111 f9=110 fa=000 -> fa=000 f9=111
                   ; 
                   ; If the player managed to release the button during "wait", before the result
                   ; was latched, it still works correctly:
                   ;   Early:   IN1=111 f9=110 fa=001 -> fa=001 f9=111
                   ; 
7c27: a4 f2                        ldy     in1_value         ;get IN1 bits (incl. player 1 fire buttons)
7c29: a5 fc                        lda     cocktail_flip     ;check cocktail / flip flags
7c2b: 29 3e                        and     #%00111110        ;are we both cocktail and player 2?
7c2d: f0 02                        beq     :Player1          ;no, branch
7c2f: a4 f1                        ldy     in0_value         ;get IN0 bits (with the player 2 fire buttons)
7c31: 98           :Player1        tya
7c32: 49 ff                        eor     #%11111111        ;do latch logic as described in comment above
7c34: 25 f9                        and     prev_plyr_in01    ; ...
7c36: 45 fa                        eor     fire_buttons
7c38: 85 fa                        sta     fire_buttons
7c3a: 84 f9                        sty     prev_plyr_in01
                   ; Manage the LEDs on the start buttons.
7c3c: a5 f5                        lda     out0_bits         ;get last value written to OUT0
7c3e: 29 40                        and     #%01000000        ;strip everything but the screen-flip bit
7c40: 09 06                        ora     #%00000110        ;set both "LED off" bits
7c42: a6 ae                        ldx     num_players       ;get number of players (0/1/2)
7c44: e8                           inx                       ;increment (now 1/2/3)
7c45: a4 93                        ldy     play_mode_flag    ;is a game in progress?
7c47: d0 10                        bne     :MaskPlyrLeds     ;yes, branch
                   ; 
7c49: a2 00                        ldx     #$00              ;start with "both dark" mask index
7c4b: a4 fb                        ldy     irq_counter       ;use IRQ count to flash LEDs
7c4d: c0 40                        cpy     #64               ;leave dark 25% of the time (~1/4 sec)
7c4f: 90 08                        bcc     :MaskPlyrLeds     ;branch to leave dark
7c51: a6 66                        ldx     credit_count      ;get number of credits paid for
7c53: e0 02                        cpx     #$02              ;1 credit flashes player 1 LED, 2 credits flashes
7c55: 90 02                        bcc     :MaskPlyrLeds     ; player 2 LED, 3+ credits flashes both
7c57: a2 03                        ldx     #$03
7c59: 3d 7e 7c     :MaskPlyrLeds   and     led_flash_masks,x ;light up appropriate LEDs
                   ; Do some coin counter housekeeping.
7c5c: a4 63                        ldy     coin_drop_ctr     ;check left coin drop counter
7c5e: 10 02                        bpl     :NotLeft          ;if value positive, branch
7c60: 09 20                        ora     #$20              ;set left coin counter bit
7c62: a4 64        :NotLeft        ldy     coin_drop_ctr+1   ;check center coin drop counter
7c64: 10 02                        bpl     :NotCenter        ;if value positive, branch
7c66: 09 10                        ora     #$10              ;set center coin counter bit
7c68: a4 65        :NotCenter      ldy     coin_drop_ctr+2   ;check right coin drop counter
7c6a: 10 02                        bpl     :NotRight         ;if value positive, branch
7c6c: 09 08                        ora     #$08              ;set right coin counter bit
7c6e: 85 f5        :NotRight       sta     out0_bits         ;save OUT0 value, to be written next IRQ
                   ; 
7c70: 2c 00 49     :VWaitLoop      bit     IN1               ;check VBLANK
7c73: 30 fb                        bmi     :VWaitLoop        ;still in VBLANK, spin until we're not
7c75: 8d 00 4d                     sta     INT_ACK           ;acknowledge IRQ
7c78: 68                           pla                       ;restore A/X/Y-reg
7c79: a8                           tay
7c7a: 68                           pla
7c7b: aa                           tax
7c7c: 68                           pla
7c7d: 40                           rti

7c7e: ff           led_flash_masks .dd1    %11111111         ;both off
7c7f: fd                           .dd1    %11111101         ;player 1 LED on
7c80: fb                           .dd1    %11111011         ;player 2 LED on
7c81: f9                           .dd1    %11111001         ;both LEDs on

                   ; 
                   ; Performs self tests.  The code switches from the main program to this when the
                   ; self-test switch is enabled.
                   ; 
                   ; On entry, interrupts are disabled.
                   ; 
                   • Clear variables

7c82: a9 00        SelfTest        lda     #$00              ;init audio
7c84: 8d 0f 40                     sta     POKEY_SKCTL
7c87: 8d 05 40                     sta     POKEY_AUDC3
7c8a: 8d 07 40                     sta     POKEY_AUDC4
7c8d: 8d 08 40                     sta     POKEY_AUDCTL
7c90: a9 03                        lda     #$03
7c92: 8d 0f 40                     sta     POKEY_SKCTL
7c95: a9 08                        lda     #%00001000        ;middle volume, some noise (sounds explody)
7c97: 8d 03 40                     sta     POKEY_AUDC2
7c9a: a9 c0                        lda     #$c0              ;low frequency
7c9c: 8d 02 40                     sta     POKEY_AUDF2
7c9f: a9 44                        lda     #%01000100        ;init screen flip, light player 1 LED
7ca1: 8d 00 48                     sta     OUT0
                   ; Make sure VBLANK is working.
7ca4: a0 60                        ldy     #96               ;do this for about 1.5 seconds
7ca6: 2c 00 49     :WaitVEnd       bit     IN1               ;check VBLANK state
7ca9: 30 fb                        bmi     :WaitVEnd         ;spin until we're not in VBLANK
7cab: 2c 00 49     :WaitVStart     bit     IN1
7cae: 10 fb                        bpl     :WaitVStart       ;now spin until we're back in VBLANK
7cb0: 8d 00 4c                     sta     WATCHDOG          ;feed the dog
7cb3: 88                           dey                       ;done yet?
7cb4: 10 f0                        bpl     :WaitVEnd         ;no, loop
                   ; Test zero page and stack RAM.  We eventually set every location to all 256
                   ; possible values.  Each pass puts a different value into every location on the
                   ; page, and different values are written to ZP/stack values at the same offset,
                   ; to check that address decoding is working.
7cb6: a9 a2                        lda     #%10100010        ;switch to pure tone, drop volume
7cb8: 8d 03 40                     sta     POKEY_AUDC2
7cbb: a0 00                        ldy     #$00              ;init test value
7cbd: a2 00        :ValueLoop      ldx     #$00              ;init memory index
7cbf: 94 00        :WriteLoop      sty     $00,x             ;write value to ZP
7cc1: c8                           iny                       ;increment value
7cc2: 98                           tya
7cc3: 9d 00 01                     sta     STACK,x           ;write value+1 to stack
7cc6: e8                           inx                       ;advance pointer
7cc7: d0 f6                        bne     :WriteLoop        ;loop until full page written, and Y-reg
7cc9: 8d 00 4c                     sta     WATCHDOG          ; is back to its initial value
                   ; 
7ccc: 98           :CheckLoop      tya                       ;get value in A-reg
7ccd: 55 00                        eor     $00,x             ;test value in ZP, result should be zero
7ccf: 95 00                        sta     $00,x             ;store the zero (to init ZP RAM for later)
7cd1: d0 1d                        bne     :BadRam?          ;wasn't zero, report failure
7cd3: c8                           iny                       ;increment value
7cd4: 98                           tya
7cd5: 5d 00 01                     eor     STACK,x           ;test value on stack, result should be zero
7cd8: 9d 00 01                     sta     STACK,x           ;store the zero to init stack RAM
7cdb: d0 13                        bne     :BadRam?          ;wasn't zero, report failure
7cdd: e8                           inx                       ;advance pointer; done yet?
7cde: d0 ec                        bne     :CheckLoop        ;no, branch
7ce0: c8                           iny                       ;Y-reg back to initial value; increment it
7ce1: d0 da                        bne     :ValueLoop        ;branch if we haven't tried all 256 values
                   ; 
7ce3: a9 40                        lda     #$40              ;change to higher frequency
7ce5: 8d 02 40                     sta     POKEY_AUDF2
7ce8: a9 42                        lda     #%01000010        ;light player 2 LED
7cea: 8d 00 48                     sta     OUT0
7ced: 4c 39 7d                     jmp     :CheckROM         ;RAM done, go check ROM

                   ; 
                   ; Report bad RAM.  The self-test mechanism plays 8 tones.  A low beep is a good
                   ; chip, a high beep is bad RAM.
                   ; 
                   ; The system has 16KB of RAM, distributed across 8 RAM chips.  Each chip is
                   ; 16Kx1, with each chip holding one bit of each byte.  By storing a value and
                   ; expecting zero, we can tell which chip is failing by checking which bit is set
                   ; to 1.
                   ; 
                   ; Chip locations (bit 7 to bit 0): P4 N4 M4 L4 K4 J4 H4 F4.
                   ; 
                   ; On entry:
                   ;   A-reg: set bits indicate failure
                   ; 
7cf0: a2 07        :BadRam?        ldx     #$07              ;repeat 8x
7cf2: a0 00                        ldy     #$00              ;silence channel 2
7cf4: 8c 03 40                     sty     POKEY_AUDC2
7cf7: 9a                           txs                       ;hold count in stack pointer (don't trust RAM)
7cf8: ba           :ErrSoundLoop   tsx
7cf9: 2a                           rol     A                 ;test high bit
7cfa: a0 a0                        ldy     #$a0              ;good tone (low pitch)
7cfc: 90 02                        bcc     :BitOk            ;bit was clear, use the good tone
7cfe: a0 10                        ldy     #$10              ;bad tone (high pitch)
7d00: 8c 00 40     :BitOk          sty     POKEY_AUDF1       ;set on channel 1
7d03: a0 a8                        ldy     #%10101000        ;pure tone, middle volume
7d05: 8c 01 40                     sty     POKEY_AUDC1       ;play tone on channel 1
                   ; Hold tone for 20 refreshes (~1/3rd of a second).
7d08: a0 20                        ldy     #$20              ;do this 20x
7d0a: 2c 00 49     :WaitVEnd1      bit     IN1               ;check VBLANK
7d0d: 30 fb                        bmi     :WaitVEnd1        ;in blank, loop
7d0f: 2c 00 49     :WaitVStart1    bit     IN1               ;check VBLANK
7d12: 10 fb                        bpl     :WaitVStart1      ;not in blank, loop
7d14: 8d 00 4c                     sta     WATCHDOG          ;feed the dog
7d17: 88                           dey                       ;done yet?
7d18: 10 f0                        bpl     :WaitVEnd1        ;no, loop
7d1a: a0 00                        ldy     #$00              ;silence channel 1
7d1c: 8c 01 40                     sty     POKEY_AUDC1
                   ; Play silence for 20 screen refreshes between each beep.
7d1f: a0 14                        ldy     #20               ;do this 20x
7d21: 2c 00 49     :WaitVEnd2      bit     IN1               ;check VBLANK
7d24: 30 fb                        bmi     :WaitVEnd2        ;in blank, loop
7d26: 2c 00 49     :WaitVStart2    bit     IN1               ;check VBLANK
7d29: 10 fb                        bpl     :WaitVStart2      ;not in blank, loop
7d2b: 8d 00 4c                     sta     WATCHDOG          ;feed the dog
7d2e: 88                           dey                       ;done yet?
7d2f: 10 f0                        bpl     :WaitVEnd2        ;no, loop
7d31: ba                           tsx
7d32: ca                           dex                       ;decrement count held in stack pointer
7d33: 9a                           txs
7d34: 10 c2                        bpl     :ErrSoundLoop
7d36: 4c 4b 7b                     jmp     HandleReset       ;restart system

                   ; 
                   ; Verify ROM checksums.
                   ]vid_ptr        .var    $00    {addr/2}
                   ]ptr            .var    $06    {addr/2}
                   ]rom_fail_map   .var    $8d    {addr/1}
                   ]odd_thing?     .var    $99    {addr/1}

7d39: a2 00        :CheckROM       ldx     #$00
7d3b: 86 06                        stx     ]ptr              ;set pointer low byte to zero
7d3d: 86 8d                        stx     ]rom_fail_map     ;init failure map
7d3f: 9a                           txs                       ;set stack pointer to zero
7d40: a9 50                        lda     #>START
7d42: 85 07                        sta     ]ptr+1            ;set high byte to start of ROM
7d44: a2 2f                        ldx     #$2f              ;page count ($5000 to 7fff)
7d46: a9 12                        lda     #18
7d48: 85 99                        sta     ]odd_thing?       ;init to 18 (why?)
                   ; 
7d4a: a0 00        :RomLoop1       ldy     #$00
7d4c: 8e 00 4c                     stx     WATCHDOG          ;feed the dog
7d4f: 51 06        :RomLoop2       eor     (]ptr),y          ;EOR all bytes in the page
7d51: c8                           iny
7d52: d0 fb                        bne     :RomLoop2
                   ; 
7d54: a8                           tay                       ;copy result to Y-reg
7d55: 8a                           txa                       ;transfer page count to A-reg
7d56: 29 07                        and     #%00000111        ;mod 8 (2KB of pages)
7d58: c9 01                        cmp     #$01              ;have we finished a ROM chip?
7d5a: 98                           tya                       ;transfer checksum result back to A-reg
7d5b: b0 18                        bcs     :NextPg           ;if page count >= 1, move to next page
7d5d: f0 10                        beq     :UpdatePg1        ;if checksum was okay, branch
                   ; Found a bad checksum.  Push the bad value on the stack.
7d5f: 48                           pha                       ;push failed value on stack (not used here)
7d60: 8a                           txa                       ;copy pages remaining (0-40) to A-reg
7d61: 48                           pha                       ;preserve X-reg
7d62: 4a                           lsr     A                 ;divide by 8 (0-5)
7d63: 4a                           lsr     A                 ; to get ROM chip number
7d64: 4a                           lsr     A
7d65: aa                           tax                       ;use as index
7d66: bd f9 60                     lda     single_bits+2,x   ;get Nth bit
7d69: 05 8d                        ora     ]rom_fail_map     ;add to failure map
7d6b: 85 8d                        sta     ]rom_fail_map
7d6d: 68                           pla                       ;restore X-reg
7d6e: aa                           tax
7d6f: a5 99        :UpdatePg1      lda     ]odd_thing?       ;add 34 (why?)
7d71: 69 22                        adc     #34               ;(carry always clear)
7d73: 85 99                        sta     ]odd_thing?       ;(34*7+18 = 256, but this isn't used?)
                   ; 
7d75: e6 07        :NextPg         inc     ]ptr+1            ;advance to next page
7d77: ca                           dex                       ;decrement page count
7d78: 10 d0                        bpl     :RomLoop1         ;branch if not done
                   ; Prepare to display text.
7d7a: 58                           cli                       ;enable interrupts
7d7b: 20 86 69                     jsr     ClearScreen       ;erase screen
7d7e: 20 2f 67                     jsr     InitLangAndFlip   ;prepare text output
7d81: a9 00                        lda     #%00000000        ;white
7d83: 85 ea                        sta     color_palette+6   ; for color index 6
7d85: a9 0e                        lda     #%00001110        ;black
7d87: 85 e4                        sta     color_palette     ; for color index 0
7d89: 85 93                        sta     play_mode_flag
                   ; 
7d8b: a9 22                        lda     #$22              ;"BAD ROM"
7d8d: ba                           tsx                       ;did we push anything on the stack?
7d8e: d0 02                        bne     :RomComm          ;yes, report bad ROM
7d90: a9 20                        lda     #$20              ;"ROM OK"
7d92: a2 ff        :RomComm        ldx     #$ff              ;reset stack pointer
7d94: 9a                           txs
7d95: 20 3b 7f                     jsr     PrintSetBits      ;print message and failed ROM numbers, if any
                   ; 
                   ; Test video memory mapping.  Write $c0 to $8080 using ZP indexed indirect
                   ; addressing to trigger MADSEL.  If the mapping is working, $11 will appear at
                   ; $2020.
7d98: a9 80                        lda     #$80              ;create pointer to $8080
7d9a: 85 00                        sta     ]vid_ptr
7d9c: 85 01                        sta     ]vid_ptr+1
7d9e: a2 00                        ldx     #$00
7da0: a9 c0                        lda     #%11000000        ;set pixel to %110 (color 6)
7da2: 81 00                        sta     (]vid_ptr,x)      ;write in MADSEL mode
7da4: a0 24                        ldy     #$24              ;"MAP OK"
7da6: ad 20 20                     lda     VIDEO_RAM_2+$19e0 ;read 2bpp pixel value
7da9: 29 11                        and     #%00010001        ;ignore adjacent pixels
7dab: c9 11                        cmp     #%00010001        ;confirm both bits were set
7dad: f0 02                        beq     :GoodVidWrite     ;if so, branch
7daf: a0 23                        ldy     #$23              ;"BAD MAP"
7db1: 98           :GoodVidWrite   tya
7db2: 20 51 6a                     jsr     PrintMessage      ;report result
                   ; 
                   ; Test random number generation.
                   ]prng_tmp       .var    $fd    {addr/1}

7db5: ad 0a 40                     lda     POKEY_RANDOM      ;generate random number
7db8: 85 fd                        sta     ]prng_tmp         ;stash it
7dba: a0 10                        ldy     #16               ;try 17x to get a different value
7dbc: ad 0a 40     :TstRndLoop     lda     POKEY_RANDOM      ;generate new random number
7dbf: c5 fd                        cmp     ]prng_tmp         ;is it the one we just generated?
7dc1: d0 0a                        bne     :NotSame          ;no, PRNG is working; branch
7dc3: 85 fd                        sta     ]prng_tmp         ;write it to ZP (why?)
7dc5: 88                           dey                       ;decrement counter
7dc6: 10 f4                        bpl     :TstRndLoop       ;loop until done
7dc8: a9 27                        lda     #$27              ;"BAD CHIP"
7dca: 20 51 6a                     jsr     PrintMessage
                   ; 
7dcd: a9 1f        :NotSame        lda     #$1f              ;"RAM OK" (we don't get this far if RAM is bad)
7dcf: 20 51 6a                     jsr     PrintMessage
                   ; 
                   ]r10_val        .var    $fd    {addr/1}
                   ]r8_val         .var    $fe    {addr/1}
                   ]draw_config_flag .var  $ff    {addr/1}

7dd2: a9 02                        lda     #$02
7dd4: 85 ae                        sta     num_players       ;set number of players to invalid value
7dd6: a9 00                        lda     #$00
7dd8: 85 b9                        sta     cur_plyr_num      ;set player 1 as current
7dda: 20 5c 65                     jsr     InitCrosshairs    ;init crosshair position
7ddd: a5 f3                        lda     r10_irq_mod       ;get R10 switches
7ddf: 49 ff                        eor     #$ff              ;invert them
7de1: 85 fd                        sta     ]r10_val          ;stash in ZP
7de3: a9 01                        lda     #$01
7de5: 85 ff                        sta     ]draw_config_flag ;set flag so we draw the config
7de7: 8d 08 40                     sta     POKEY_AUDCTL      ;set frequency divider rate to 15KHz
                   ; 
                   ; Main loop for test code.  We wake on every IRQ and check for changes.
                   ; 
7dea: 46 9f        :TestIdleLoop   lsr     exec_flag         ;spin until the IRQ tells us to go
7dec: 90 fc                        bcc     :TestIdleLoop
                   ; 
                   ; Check for some special button combinations.  These don't appear to be
                   ; documented in the manual.
7dee: a5 f2                        lda     in1_value         ;get processed IN1 value
7df0: 29 24                        and     #%00100100        ;slam switch and left fire button?
7df2: d0 05                        bne     :NotGrid          ;no, branch
7df4: 85 ff                        sta     ]draw_config_flag ;set to zero
7df6: 20 7f 7f                     jsr     Test_DrawGrid     ;draw grid
                   ; 
7df9: a5 f2        :NotGrid        lda     in1_value         ;get processed IN1 value
7dfb: 29 21                        and     #%00100001        ;slam switch and right fire button?
7dfd: d0 05                        bne     :NotBars          ;no, branch
7dff: 85 ff                        sta     ]draw_config_flag ;set to zero
7e01: 20 62 7f                     jsr     Test_DrawBars     ;draw color bars
                   ; 
7e04: a9 00        :NotBars        lda     #$00
7e06: 8d 01 40                     sta     POKEY_AUDC1       ;silence channels 1 and 2
7e09: 8d 03 40                     sta     POKEY_AUDC2
7e0c: a5 f2                        lda     in1_value         ;get processed IN1 value
7e0e: 29 22                        and     #%00100010        ;slam switch and middle fire button?
7e10: d0 05                        bne     :NotScan          ;no, branch
7e12: 85 ff                        sta     ]draw_config_flag ;set to zero
7e14: 20 eb 7e                     jsr     Test_ScanVideo    ;draw bars then scan down the screen
                   ; 
                   ; React to the use of the controls.
                   ; 
7e17: 20 32 51     :NotScan        jsr     UpdateCrossPosn   ;move crosshairs when trackball moves
7e1a: a5 f1                        lda     in0_value         ;get IN0 value (switches)
7e1c: 49 ff                        eor     #$ff              ;invert bits
7e1e: 8d 00 40                     sta     POKEY_AUDF1       ;set as frequency on channel 1
7e21: f0 07                        beq     :NoBut0           ;if no buttons pressed, branch
7e23: a9 a8                        lda     #%10101000        ;at least one button pressed, enable sound
7e25: 8d 01 40                     sta     POKEY_AUDC1       ;medium volume, pure tone
7e28: e6 e4                        inc     color_palette     ;change value of color #0
                   ; 
7e2a: a5 f2        :NoBut0         lda     in1_value         ;get IN1 value
7e2c: 49 ff                        eor     #$ff              ;invert bits
7e2e: 29 27                        and     #%00100111        ;mask off all but slam switch and fire buttons
7e30: 8d 02 40                     sta     POKEY_AUDF2       ;set as frequency on channel 2
7e33: f0 07                        beq     :NoBut1           ;if no buttons pressed, branch
7e35: a9 a8                        lda     #%10101000        ;at least one button pressed, enable sound
7e37: 8d 03 40                     sta     POKEY_AUDC2       ;medium volume, pure tone
7e3a: e6 e4                        inc     color_palette     ;change value of color #0
                   ; 
7e3c: a5 fa        :NoBut1         lda     fire_buttons      ;get fire buttons for current player
7e3e: 29 07                        and     #%00000111        ;mask just to be sure
7e40: f0 17                        beq     :NoFire           ;if none pressed, branch
7e42: a5 b9                        lda     cur_plyr_num      ;toggle current player number
7e44: 49 01                        eor     #%00000001        ;this flips the screen over in cocktail mode
7e46: 85 b9                        sta     cur_plyr_num
7e48: a9 00                        lda     #$00
7e4a: 85 fa                        sta     fire_buttons      ;reset fire buttons
7e4c: 20 86 69                     jsr     ClearScreen       ;clear screen
7e4f: a5 fd                        lda     ]r10_val          ;get R10 switches
7e51: 49 ff                        eor     #$ff              ;invert bits
7e53: 85 fd                        sta     ]r10_val          ;save it back
7e55: a9 01                        lda     #$01
7e57: 85 ff                        sta     ]draw_config_flag ;set to nonzero
                   ; 
7e59: 20 6b 7e     :NoFire         jsr     Test_ShowConfig
7e5c: 24 f2                        bit     in1_value         ;check the self-test switch
7e5e: 50 8a                        bvc     :TestIdleLoop     ;still enabled, loop
7e60: 4c 4b 7b                     jmp     HandleReset       ;no longer enabled, reset system

                   ; 
                   ; Coin slot multiplier digits.  The center slot can be configured for 1x or 2x,
                   ; the right slot for 1x/4x/5x/6x.  The bytes below contain all 8 possibilities.
                   coin_mult_digits
7e63: 11 14 15 16+                 .bulk   $11,$14,$15,$16,$21,$24,$25,$26

                   ; 
                   ; Draws the system configuration if $ff is nonzero, or if R8/R10 have changed
                   ; since the last time.
                   ; 
7e6b: a5 ff        Test_ShowConfig lda     ]draw_config_flag ;do we want to draw the config?
7e6d: f0 7b                        beq     :Return           ;no, bail
7e6f: a5 f3                        lda     r10_irq_mod       ;get latest R10 values
7e71: c5 fd                        cmp     ]r10_val          ;does it match previous read?
7e73: 85 fd                        sta     ]r10_val          ;update previous
7e75: d0 06                        bne     :Mismatch         ;mismatch, branch
7e77: a5 f4                        lda     r8_irq_inv        ;get latest R8 values
7e79: c5 fe                        cmp     ]r8_val           ;does it match previous read?
7e7b: 85 fe                        sta     ]r8_val           ;update previous
7e7d: f0 6b        :Mismatch       beq     :Return           ;matched, bail
                   ; 
7e7f: a9 25                        lda     #$25              ;erase lines with these messages
7e81: 20 49 6a                     jsr     EraseMessageLine  ;"FREE PLAY"
7e84: a9 1b                        lda     #$1b
7e86: 20 49 6a                     jsr     EraseMessageLine  ;"BONUS CITY"
7e89: 20 2f 67                     jsr     InitLangAndFlip   ;init language, and flip based on current player
                   ; Draw coin slot multipliers.
7e8c: a5 f3                        lda     r10_irq_mod       ;get R10 switches
7e8e: 29 03                        and     #%00000011        ;mask all but the coins-per-play bits
7e90: aa                           tax
7e91: bd 2f 6c                     lda     play_cost_msg_tbl,x ;get the appropriate message ID
7e94: 20 51 6a                     jsr     PrintMessage
7e97: a5 f3                        lda     r10_irq_mod       ;get R10 switches
7e99: 29 1c                        and     #%00011100        ;mask to get coin mechanism config
7e9b: 4a                           lsr     A                 ;shift to put value in low bits
7e9c: 4a                           lsr     A
7e9d: aa                           tax                       ;use as index
7e9e: bd 63 7e                     lda     coin_mult_digits,x ;get digits
7ea1: 48                           pha                       ;preserve value
7ea2: 4a                           lsr     A                 ;shift 4x to get center slot multiplier
7ea3: 4a                           lsr     A
7ea4: 4a                           lsr     A
7ea5: 4a                           lsr     A
7ea6: a2 40                        ldx     #64               ;screen position
7ea8: a0 28                        ldy     #40
7eaa: 20 9d 6a                     jsr     DrawSmallNum      ;draw number
7ead: 68                           pla                       ;restore value
7eae: 29 07                        and     #%00000111        ;mask excess to get right slot multiplier
7eb0: a2 50                        ldx     #80               ;position a little to the right
7eb2: 20 9d 6a                     jsr     DrawSmallNum      ;draw number
                   ; 
                   ]horz_posn      .var    $1d    {addr/1}
                   ]vert_posn      .var    $1e    {addr/1}

7eb5: a9 70                        lda     #112              ;horizontal position
7eb7: 85 1d                        sta     ]horz_posn
7eb9: a9 28                        lda     #40               ;vertical position
7ebb: 85 1e                        sta     ]vert_posn
7ebd: a5 f4                        lda     r8_irq_inv        ;get R8 switches
7ebf: 29 08                        and     #%00001000        ;mask off mini-trakball setting
7ec1: f0 02                        beq     :NotMini          ;not set, branch
7ec3: a9 46                        lda     #‘F’              ;display 'F' for mini trackball
7ec5: 20 ba 76     :NotMini        jsr     PrintPaddedChar   ;or draw blank space for A-reg=0
                   ; 
7ec8: a5 f4                        lda     r8_irq_inv        ;get R8 switches
7eca: 29 04                        and     #%00000100        ;mask off bonus credit for 4 coins setting
7ecc: f0 02                        beq     :NoCred           ;not set, branch
7ece: a9 58                        lda     #‘X’              ;display 'X' if set
7ed0: 20 ba 76     :NoCred         jsr     PrintPaddedChar   ;or draw blank space for A-reg=0
                   ; 
7ed3: a5 f4                        lda     r8_irq_inv        ;get R8 switches
7ed5: 29 03                        and     #%00000011        ;get setting for number of starting cities
7ed7: aa                           tax
7ed8: bd 08 5b                     lda     init_city_count,x ;get count
7edb: a2 40                        ldx     #64
7edd: a0 34                        ldy     #52
7edf: 20 9d 6a                     jsr     DrawSmallNum      ;draw number
7ee2: a9 26                        lda     #$26              ;"CITIES"
7ee4: 20 51 6a                     jsr     PrintMessage
7ee7: 20 aa 74                     jsr     PrintBonusCityPts ;print number of points per bonus city
7eea: 60           :Return         rts

                   ; 
                   ; Exercises video RAM.  Draws color bars, then clears the screen, then walks
                   ; through all of video memory.  For every byte, we set the byte, test the next
                   ; 255 bytes to see if they're still zero, test the byte to see if it's still
                   ; set, then clear the byte.
                   ; 
                   • Clear variables
                   ]bad_bits       .var    $8d    {addr/1}
                   ]message        .var    $99    {addr/1}

7eeb: 20 62 7f     Test_ScanVideo  jsr     Test_DrawBars     ;draw the color bars with MADSEL writes
7eee: 20 86 69                     jsr     ClearScreen       ;clear the screen with normal writes
7ef1: 78                           sei                       ;disable interrupts
7ef2: a9 00                        lda     #$00              ;init bad bit set
7ef4: 85 8d                        sta     ]bad_bits
7ef6: a9 1f                        lda     #$1f              ;"RAM OK"
7ef8: 85 99                        sta     ]message
7efa: a9 02                        lda     #$02              ;start at $0200
7efc: 85 07                        sta     gfx_ptr+1         ;(low byte and Y-reg zeroed by screen clear)
                   ; 
7efe: a9 aa        :TestLoop       lda     #$aa              ;alternating bit pattern
7f00: 91 06                        sta     (gfx_ptr),y       ;write to video memory area
7f02: c8                           iny                       ;advance pointer
                   ; 
7f03: b1 06        :ReadLoop       lda     (gfx_ptr),y       ;get next byte, which should be zero
7f05: f0 03                        beq     :ReadOk           ;branch if so
7f07: 20 32 7f                     jsr     :Bad2             ;note bad pattern
7f0a: c8           :ReadOk         iny                       ;advance pointer
7f0b: d0 f6                        bne     :ReadLoop         ;loop until we've read 255 entries
                   ; 
7f0d: b1 06                        lda     (gfx_ptr),y       ;get original entry
7f0f: c9 aa                        cmp     #$aa              ;is it what we set it to?
7f11: f0 03                        beq     :WriteOk          ;yes, branch
7f13: 20 30 7f                     jsr     :Bad1             ;note bad pattern
                   ; 
7f16: a9 00        :WriteOk        lda     #$00
7f18: 91 06                        sta     (gfx_ptr),y       ;set byte back to zero
7f1a: 85 9f                        sta     exec_flag         ;clear the exec flag (shouldn't matter, IRQ disabled)
7f1c: 8d 00 4c                     sta     WATCHDOG          ;feed the dog
7f1f: e6 06                        inc     gfx_ptr           ;advance pointer one byte
7f21: d0 db                        bne     :TestLoop         ;loop over all entries on this page
7f23: e6 07                        inc     gfx_ptr+1         ;increment high byte of pointer
7f25: a5 07                        lda     gfx_ptr+1
7f27: c9 3f                        cmp     #$3f              ;have we reached the end of video RAM?
7f29: d0 d3                        bne     :TestLoop         ;not yet, loop
7f2b: a5 99                        lda     ]message
7f2d: 4c 3b 7f                     jmp     PrintSetBits      ;display message and failed bits, if any

7f30: 49 aa        :Bad1           eor     #$aa              ;flip bits we stored
7f32: 05 8d        :Bad2           ora     ]bad_bits         ;add in the bad bits
7f34: 85 8d                        sta     ]bad_bits
7f36: a9 21                        lda     #$21              ;"BAD RAM"
7f38: 85 99                        sta     ]message
7f3a: 60                           rts

                   ; 
                   ; Prints set bits, as a series of single-digit numbers (0-7), vertically upward.
                   ; 
                   ; On entry:
                   ;   A-reg: message index
                   ;   $8d: bit map
                   ; 
                   • Clear variables
                   ]bit_set        .var    $8d    {addr/1}
                   ]saved_index    .var    $c5    {addr/1}

7f3b: 58           PrintSetBits    cli                       ;enable interrupts
7f3c: 20 51 6a                     jsr     PrintMessage      ;print message
7f3f: a5 8d                        lda     ]bit_set          ;get the set of bits
7f41: a2 08                        ldx     #$08              ;walk through all 8
7f43: 0a           :Loop           asl     A                 ;shift high bit into carry
7f44: 90 18                        bcc     :Next             ;not set, branch
7f46: 48                           pha                       ;preserve A-reg
7f47: 86 c5                        stx     ]saved_index      ;preserve X-reg
7f49: a9 e0                        lda     #%11100000        ;color = 7 (bug? should be 6)
7f4b: 85 0b                        sta     draw_fg_color
7f4d: 8a                           txa                       ;get bit index in A-reg
7f4e: 0a                           asl     A                 ;multiply by 8 (the width of a glyph)
7f4f: 0a                           asl     A
7f50: 0a                           asl     A
7f51: 18                           clc
7f52: 69 88                        adc     #136              ;add base vertical position
7f54: a8                           tay                       ;copy to Y-reg
7f55: 8a                           txa                       ;use bit index (1-8) as number to draw
7f56: a2 a0                        ldx     #160              ;set horizontal position
7f58: 20 9d 6a                     jsr     DrawSmallNum      ;draw number
7f5b: a6 c5                        ldx     ]saved_index      ;restore bit index
7f5d: 68                           pla                       ;restore bit set
7f5e: ca           :Next           dex                       ;decrement bit index
7f5f: d0 e2                        bne     :Loop             ;loop until done
7f61: 60                           rts

                   ; 
                   ; Draws color bars.
                   ; 
7f62: a2 07        Test_DrawBars   ldx     #$07              ;set 8 colors
7f64: 8a           :ColorLoop      txa
7f65: 0a                           asl     A                 ;double index
7f66: 95 e4                        sta     color_palette,x   ;use as color value
7f68: ca                           dex
7f69: 10 f9                        bpl     :ColorLoop
                   ; 
7f6b: e8                           inx                       ;X-reg=0
7f6c: 86 06                        stx     gfx_ptr           ;init low byte of pointer
7f6e: a0 18        :Loop1          ldy     #$18              ;(should start at $1900? bug?)
7f70: 84 07                        sty     gfx_ptr+1
7f72: a5 06        :Loop2          lda     gfx_ptr           ;use the high 2 bits of the low byte of the
7f74: 81 06                        sta     (gfx_ptr,x)       ; pointer as the color index
7f76: e6 07                        inc     gfx_ptr+1         ;incr high byte
7f78: d0 f8                        bne     :Loop2            ; until we walk off the end of memory
7f7a: e6 06                        inc     gfx_ptr           ;incr low byte
7f7c: d0 f0                        bne     :Loop1            ;loop, resetting high byte
7f7e: 60                           rts

                   ; 
                   ; Draws a 5x5 test grid that spans the full screen.
                   ; 
                   ]hindex         .var    $98    {addr/1}

7f7f: a9 0e        Test_DrawGrid   lda     #%00001110        ;black
7f81: a2 07                        ldx     #$07
7f83: 95 e4        :Loop           sta     color_palette,x   ;set all color values
7f85: ca                           dex
7f86: 10 fb                        bpl     :Loop
7f88: a9 00                        lda     #%00000000        ;white
7f8a: 85 e6                        sta     color_palette+2   ;set color #2
7f8c: 20 86 69                     jsr     ClearScreen       ;erase screen to color 0
                   ; 
7f8f: a2 00                        ldx     #$00
7f91: a0 05                        ldy     #$05              ;drawing 6 vertical lines
7f93: a9 19        :HGridLoop      lda     #$19              ;top line is at $1900
7f95: 85 07                        sta     gfx_ptr+1         ;set as high byte of pointer
7f97: b9 e2 7f                     lda     :test_grid_horz,y ;get horizontal offset of this line
7f9a: 85 06                        sta     gfx_ptr           ;set low byte of pointer
7f9c: a9 40                        lda     #%01000000        ;color 2
7f9e: 81 06        :HLineLoop      sta     (gfx_ptr,x)       ;set pixel
7fa0: e6 07                        inc     gfx_ptr+1         ;advance high byte to move down screen
7fa2: d0 fa                        bne     :HLineLoop        ;continue until high byte rolls over
7fa4: 88                           dey                       ;move to next line
7fa5: 10 ec                        bpl     :HGridLoop        ;loop until done
                   ; 
7fa7: a0 05                        ldy     #$05              ;drawing 6 horizontal lines
7fa9: b9 e8 7f     :VGridLoop      lda     :test_grid_vert,y ;get high byte of row address
7fac: 85 07                        sta     gfx_ptr+1         ;set pointer
7fae: 86 06                        stx     gfx_ptr           ;set low byte to zero
7fb0: a9 40                        lda     #%01000000        ;color 2
7fb2: 81 06        :VLineLoop      sta     (gfx_ptr,x)       ;set pixel
7fb4: e6 06                        inc     gfx_ptr           ;move across row
7fb6: d0 fa                        bne     :VLineLoop        ;continue until we reach the end
7fb8: 88                           dey                       ;move to next line
7fb9: 10 ee                        bpl     :VGridLoop        ;loop until done
                   ; Put dots in the boxes.
7fbb: a0 04                        ldy     #$04              ;5 boxes across
7fbd: b9 e2 7f     :DotLoop1       lda     :test_grid_horz,y ;get left side of box
7fc0: 18                           clc
7fc1: 79 e3 7f                     adc     :test_grid_horz+1,y ;add to right side of box
7fc4: 6a                           ror     A                 ;divide by 2 to get center
7fc5: 85 06                        sta     gfx_ptr           ;set as pointer low byte
7fc7: 84 98                        sty     ]hindex           ;save index
                   ; 
7fc9: a0 04                        ldy     #$04              ;5 boxes high
7fcb: b9 e8 7f     :DotLoop2       lda     :test_grid_vert,y ;get top row pointer
7fce: 18                           clc
7fcf: 79 e9 7f                     adc     :test_grid_vert+1,y ;add bottom row pointer
7fd2: 6a                           ror     A                 ;divide by 2 to get center
7fd3: 85 07                        sta     gfx_ptr+1         ;set as pointer high byte
7fd5: a9 40                        lda     #%01000000        ;color 2
7fd7: 81 06                        sta     (gfx_ptr,x)       ;set pixel
7fd9: 88                           dey                       ;done all boxes in this column?
7fda: 10 ef                        bpl     :DotLoop2         ;not yet, loop
7fdc: a4 98                        ldy     ]hindex           ;get horizontal index
7fde: 88                           dey                       ;done all columns?
7fdf: 10 dc                        bpl     :DotLoop1         ;not yet, loop
7fe1: 60                           rts

                   ; 
                   ; Vertical and horizontal positions for grid lines, stored as pointer values.
                   ; 
7fe2: 00 33 66 99+ :test_grid_horz .bulk   $00,$33,$66,$99,$cc,$ff
7fe8: 19 46 74 a2+ :test_grid_vert .bulk   $19,$46,$74,$a2,$d0,$ff
                   ; 
7fee: d8                           .dd1    $d8               ;checksum byte
7fef: 00 00 00 00+                 .junk   11                ;wasted space!
                   ; 
                   ; 6502 vectors.
                   ; 
7ffa: 4b 7b                        .dd2    HandleReset       ;NMI vector
7ffc: 4b 7b                        .dd2    HandleReset       ;reset vector
7ffe: 2a 7b                        .dd2    HandleIRQ         ;IRQ vector
                                   .adrend ↑ $5000

Symbol Table

HandleIRQ$7b2a
HandleReset$7b4b
START$5001