; ============================================================================
; $0400-$07FF - manual-check answer table, status message strings and shared scratch area
; ============================================================================
; Copied from $6F00 (track 27 sectors 13-16) by the boot loader. $0401 holds the 32 x 3-byte answers for
; the manual look-up check, $0461 the memory checksum routine the boot loader returns into, $0500-$0567
; the status message pointer tables and $056C.. the status message strings. Once the battle starts the
; three 50-byte arrays at $0400/$0432/$0464 are reused as unit scan lists and AI threat maps.

        .setcpu "6502"
        .include "c64.inc"
        .include "zeropage.inc"

; ---- references to code/data outside this file ----
gameEntry                := $0800
D_09C3                   := $09C3
inputLockoutTimer        := $0B7D
programChecksum          := $0B7E
filmPlaybackMode         := $0B9B
gameEndReason            := $0BA8
resumeMainMenuScreen     := $1414
enterMainMenu            := $141D
enterMapScreen           := $1FC3
runMapMainLoop           := $1FD0
drawViewport             := $26C1
initBitmapScreen         := $2D41
clearMessageLineAndScreen := $2DDB
updateGameFrame          := $40B2
drawOverviewMap          := $5AD7
runMissileScreen         := $7587
missileScreenResume      := $759C
enterDroneScreen         := $7FE6
resetDroneScreen         := $7FF6
releaseDroneControl      := $8230
msgGetOpponentOnPhoneAnd := $8800
spriteXMsbShadow         := $9020
spriteEnableShadow       := $9022
spriteXExpandShadow      := $9023
spriteYExpandShadow      := $9024
currentScreen            := $90FB
stepDelayTimer           := $90FC
unitOrderIndex           := $9162
comcenStunStatus         := $922A
chatState                := $929C
gameEndCountdown         := $92C9
droneArrivalFlags        := $92F9
queueMessage             := $C566

; Contents
; --------
;    $0461  verifyProgramChecksum        boot-time integrity check, and the address the boot loader falls into rather
;                                        than calls.
;    $06BA  switchToRequestedScreen      the game's screen dispatcher, called once per iteration from every screen's
;                                        main loop to honour a pending F1/F3/F5/F7 keypress.
;    $0709  runComcenStunnedBlackout     the 'COMCEN STUNNED!' cut-scene: when your command centre has been knocked out,
;                                        control is taken away, the screen goes blank and the engine keeps running unwatched for exactly one full round of all
;                                        100 units before the game hands control back.

        .org $0400


; checksumXorConstant: 1 byte, $B0. The constant verifyProgramChecksum folds into the finished sum at
; $04A0 before storing it in programChecksum ($0B7E). It is deliberately OUTSIDE the checked range,
; which starts at $0402, so this byte alone shifts the reported build signature without changing the
; sum itself. The whole $0400-$07FF page is a copy of $6F00-$72FF (track 27 sectors 13-16), made by
; the boot loader's four-page block move at $C1B3-$C1C3 just before it drops into the checksum below.
; Once a battle starts the first half of the page is pure scratch and this byte becomes an array
; element - see the page map in the block comment at $04AE.
checksumXorConstant:
        .byte   $B0                     ; 0400  $B0 = the checksum EOR constant; at run time this is also element 0 of the $0400 scratch arrays

; mapNameSeedTable: 32 entries x 3 bytes = 96 bytes ($0401-$0460), the manual look-up protection's
; question bank, used only by the copy-protection routine at $09A6.
; $09C2 executes 'LDA #seed' whose immediate operand at $09C3 is a SID noise byte the boot loader
; poked in; AND #$1F picks entry 0-31 and entry*3 indexes this table ($09CC/$09D3/$09DA).  The three
; bytes are stored as the 24-bit map seed mapSeed/mapSeed1/mapSeed2 ($0B95-$0B97),
; generateBattlefieldMap draws that battlefield, and the player has to type the map's name from the
; manual; enterMapName packs what was typed back into a 24-bit number (zp_5A-zp_5C) and $09FC-$0A0F
; compares it with the very same three bytes.  So there is no separate answer list: a map name IS the
; seed, written in base 32 with the 32-character alphabet at $890C.  Three wrong answers and $0A1E
; wipes all of memory. Note that the checksum below starts at $0402, so the first byte of entry 0 is
; not covered by it. None of this data survives into the game: the labels inside the table ($0410,
; $0420, $0430, $0432,
; $0450) are there because other code uses those addresses as scratch arrays later on.
mapNameSeedTable:
        .byte   $0D,$0F,$AE,$97,$CD,$CE,$E4,$92; 0401  ........
        .byte   $24,$24,$D9,$40,$3C,$CC,$EC; 0409  $$.@<..

; Map-name entry 5 of the table above starts here.  At run time $0410+g is the group summariser's
; 'worst movement cost any member of group g has to pay' (g = 0-15): cleared at $48ED, raised at $4942
; from terrainMoveCostTable[terrain class under the member].
D_0410:
        .byte   $CE,$1D,$AC,$F4,$89,$27,$54,$86; 0410  .....'T.
        .byte   $82,$BB,$B6,$86,$B2,$58,$C7,$E3; 0418  .....X..

; (This address falls inside map-name entry 10.)  At run time $0420+g holds the lowest movement rate
; found in group g (g = 0-15): preset to $FF at
; $48F2 and lowered at $4960, so the whole group ends up moving at the speed of its slowest member.
D_0420:
        .byte   $6A,$5A,$B5,$94,$AF,$85,$CF,$DF; 0420  jZ......
        .byte   $3D,$DF,$84,$2E,$F3,$AA,$2E,$B0; 0428  =.......

; (The last byte of map-name entry 15.)  At run time $0430+g is the copy of groupStatusTable[g] saved
; at $48E2 before the group table is rebuilt; only bits 6-7 of the old value are folded back into the
; new one at $4978.
D_0430:
        .byte   $4C,$B2                 ; 0430  L.

; (Map-name entry 16 starts here.)  At run time $0432+n is the squared-distance list that runs
; parallel to the unit-index list at $0400+n: collectUnitsInRadius ($37AA) fills both ($3806/$380E),
; and scanVisibleEnemies penalises an entry in place at $364F when the target is not in the unit's
; facing direction.
D_0432:
        .byte   $9C,$A0,$25,$39,$17,$9A,$26,$26; 0432  ..%9..&&
        .byte   $61,$6C,$6D,$B9,$15,$57,$CF,$25; 043A  alm..W.%
        .byte   $32,$E7,$91,$8D,$90,$42,$5F,$2E; 0442  2....B_.
        .byte   $05,$C6,$B6,$2B,$1D,$68 ; 044A  ...+.h

; At run time $0450+d is the row half of the map generator's flood-fill stack: $0400+d = column,
; $0450+d = row, $04A0+d = the direction bits still to try, d = 0..$4F (80 deep, $71C6-$72EF).
D_0450:
        .byte   $25,$27,$0E,$DB,$BC,$D3,$FB,$9B; 0450  %'......
        .byte   $87,$FE,$B9,$2F,$23,$C2,$E2,$D8; 0458  .../#...
        .byte   $81                     ; 0460  .

; ----------------------------------------------------------------------
; verifyProgramChecksum - boot-time integrity check, and the address the boot loader falls into rather
; than calls.  While loading, the loader rebuilt the stack (the RTS opcode $60 read from bootDone is
; pushed by the patched PHA at $C1B1, $04 was stored at $01FE by $C26C), so the RTS at $C218 pops the
; return address $0460 and execution resumes here at $0461.  It sums $0402-$87FF - everything from the
; protection table up to the last byte of whichever $6F00-$87FF overlay is resident - with a
; carry-chained ADC/EOR/ROL word loop, parks the raw sum in $8800 (the first byte past the checked
; range) and stores sum EOR $B0 in programChecksum $0B7E, then enters the game.  $0B7E is never tested
; locally: it is sent to the opponent in the settings packet ($ED89 in the modem driver) so both
; machines can notice they are running different builds.
; In:  memory $0402-$87FF; $09C3 (the manual-check seed byte, zeroed for the duration so the sum does
;     not depend on it); $0400 = $B0; a stack rigged to return to $0460
; Out: $8800 = the raw sum ($69 on this disk image), $0B7E = sum EOR $B0 ($D9 here), $09C3 restored,
;     zp_19/zp_1A/zp_1B clobbered, the two self-modified operand high bytes at $047C and $0484 left
;     pointing at page $88; ends with JMP $0800 (Y = 2, S = $FE)
; Called from: nobody - it is entered by the boot loader's obfuscated RTS.  Recomputing the sum from
;     the extracted image gives $69/$D9, while the byte mastered at $0B7E on this d64 is $DB and
;     overlay A preloads the same $DB at $79CC, so this disk image has been altered somewhere in
;     $0402-$87FF.
; ----------------------------------------------------------------------
verifyProgramChecksum:
        lda     D_09C3                  ; 0461  read the manual-check seed byte - it is the immediate operand of the 'LDA #' at $09C2, poked in by the boot loader from SID noise
D_0464:
        sta     scratch19               ; 0464  park the seed in zp_19; it is zeroed below so that the sum is the same on every boot, and put back at $04A6  (the address $0464 itself becomes a scratch array later - see the page map at $04AE)
        lda     SID_OSC3_RANDOM         ; 0466  read the SID voice-3 noise oscillator $D41B - free random bits
        and     #$7F                    ; 0469  keep bits 0-6
        sta     scratch1A               ; 046B  store it in zp_1A and never look at it again: no code in this routine reads zp_1A and every later user of it writes before reading, so these three instructions look like dead leftovers, an old random-seed grab
        ldx     #$84                    ; 046D  132 pages to fold in: $04 up to and including $87, i.e. everything up to the end of the resident $6F00-$87FF overlay - at boot time that is the comcen-screens overlay the loader has just installed
        lda     #$00                    ; 046F  A = 0: the running sum starts empty (the same zero also clears the carry counter and the seed byte)
        sta     scratch1B               ; 0471  zp_1B counts how many times the ADC carried out; the count is added back into the sum at $049B
        tay                             ; 0473  Y = 0
        iny                             ; 0474  Y = 1 ...
        iny                             ; 0475  ... Y = 2: skip $0400 (the EOR constant) and $0401 (the first protection byte) so the sum starts at $0402
        sta     D_09C3                  ; 0476  zero the seed byte inside the instruction at $09C2, otherwise every boot would checksum differently
        clc                             ; 0479  clear the carry ONCE, outside the loop: the carry out of each word is deliberately carried into the next one
L_047A:
        adc     checksumXorConstant,y   ; 047A  add the even byte of the word; the operand high byte at $047C is bumped one page per outer pass, so this reads $0402, $0404 ... $87FE
        bcc     L_0481                  ; 047D  branch if this addition did not carry out
        inc     scratch1B               ; 047F  it did: count the carry instead of losing it
L_0481:
        iny                             ; 0481  step to the odd byte of the word
L_0482:
        eor     checksumXorConstant,y   ; 0482  mix the odd byte in with EOR (so byte order matters); its operand high byte at $0484 is bumped together with the other one
        php                             ; 0485  save the ADC carry - the rotate below must not disturb it
        asl     a                       ; 0486  start of a hand-made ROL A ...
        bcc     L_048B                  ; 0487  ... branch if bit 7 was clear
        ora     #$01                    ; 0489  ... bit 7 fell out of the top, so feed it back into bit 0
L_048B:
        plp                             ; 048B  restore the ADC carry for the next word  (the address $048C is one of the trainer's accumulator arrays later - see $04AE)
D_048C:
        iny                             ; 048C  on to the next word
        bne     L_047A                  ; 048D  128 words = one page: keep going until Y wraps back to 0
        inc     L_047A+2                ; 048F  self-modifying: bump the high byte of the ADC operand (the byte at $047C) to the next page
        inc     L_0482+2                ; 0492  self-modifying: bump the high byte of the EOR operand (the byte at $0484) the same way
        dex                             ; 0495  one page done
        bne     L_047A                  ; 0496  loop over all 132 pages; both operands are left pointing at page $88 when this falls through
        ldy     #$02                    ; 0498  Y = 2 on entry to the game - mainStart never reads Y, so this looks like a leftover
        clc                             ; 049A  clear carry for the final add
        adc     scratch1B               ; 049B  add the carry count back in, so no carry out of the chain is lost
        sta     msgGetOpponentOnPhoneAnd; 049D  park the raw sum in $8800 - the byte immediately after the checked range.  Nothing ever reads it back and the modem status strings are loaded over $8800 later, so this store is only a convenient scratch byte
D_04A0:
        eor     checksumXorConstant     ; 04A0  fold in the $B0 at $0400, the one byte of this page that the sum deliberately skipped  (this address is also the trainer's fourth accumulator array and the map generator's fill-stack direction array later)
        sta     programChecksum         ; 04A3  $0B7E = the build signature handed to the opponent during the settings handshake; $D9 on this image, $DB on an unmodified one
        lda     scratch19               ; 04A6  take the seed byte back out of zp_19 ...
        sta     D_09C3                  ; 04A8  ... and restore the immediate operand at $09C3 so the manual check still picks a random map
        jmp     gameEntry               ; 04AB  into the game proper: $0800 is a JMP to mainStart ($0A6C), which sets its own stack pointer to $FD


; scratchPageTail: 82 bytes of $00 filler on disk ($04AE-$04FF) - everything after the checksum
; routine. Nothing in $0400-$04FF is read as data by the running game: in a solo game the trainer's
; playbook (track 29 sectors 6-8) is loaded straight over $0200-$04FF, and in every game the page is
; scratch. The tenants of the page, all of which are only valid inside the routine that uses them,
; are:
;   $0400+n / $0432+n / $0464+n   unit scan lists (index, squared distance, direction) built by
;                                 collectUnitsInRadius $37AA and scanVisibleEnemies $35FE
;   $0400+unit / $0464+sp         a per-unit deferral counter (a unit whose count is non-zero is put
;                                 off) and the stack of units it is waiting on, while finishRound
;                                 rebuilds the next round's process order ($4665-$4750)
;   $0400+g / $0410+g / $0420+g / $0430+g   per-group (0-15) member count, worst move cost, lowest
; move
;                                 rate and saved status byte ($48DD-$4986)
;   $0400+d / $0450+d / $04A0+d   the map generator's flood-fill stack: column, row, remaining
;                                 directions, up to 80 deep ($71C6-$72EF)
;   $0464+i / $0478+i / $048C+i / $04A0+i   the solo trainer's four 12-entry 16-bit accumulators -
; these
;                                 are the high bytes, the low bytes live at $88D2/$88DE/$88EA/$88F6
;                                 (all cleared together at $ED93)
;   $04C8-$04E7                   the 32-byte tile compose buffer described below
scratchPageTail:
        .byte   $00,$00,$00,$00,$00,$00,$00,$00; 04AE  ........
        .byte   $00,$00,$00,$00,$00,$00,$00,$00; 04B6  ........
        .byte   $00,$00,$00,$00,$00,$00,$00,$00; 04BE  ........
        .byte   $00,$00                 ; 04C6  ..

; Tile compose buffer, 32 bytes ($04C8-$04E7): one battlefield cell drawn at 16x16 pixels, i.e. the
; 2x2 character cells of the multicolour bitmap that the tactical viewport uses.  Layout: bytes
; $00-$07 = top-left character, $08-$0F = top-right, $10-$17 = bottom-left, $18-$1F = bottom-right.
; drawViewport builds a cell here: the 4x4-pixel terrain tile is expanded, each pixel doubled in both
; directions ($27C2-$27E5 - the high nibble of each source byte becomes the left character, the low
; nibble the right one, and every row is written to two consecutive scan lines), the unit glyph is
; ORed on top ($28B5-$28BC), and the finished 32 bytes are blitted into the bitmap by drawViewport
; ($2700, the full 7x5 redraw) and drawViewportCell ($5D54, a single cell), both of which step the
; bitmap pointer on to the next character row after the first 16 bytes.
D_04C8:
        .byte   $00                     ; 04C8  .

; +$01: the odd scan lines of the top-left character; each expanded terrain row is stored at both
; $04C8+x and $04C9+x so that it is two pixels tall.
D_04C9:
        .byte   $00,$00,$00,$00,$00,$00,$00; 04C9  .......

; +$08: first scan line of the top-right character (the low nibble of the same source byte).
D_04D0:
        .byte   $00                     ; 04D0  .

; +$09: its odd scan lines, written together with $04D0+x.
D_04D1:
        .byte   $00,$00,$00,$00,$00,$00,$00,$00; 04D1  ........
        .byte   $00,$00,$00,$00,$00,$00 ; 04D9  ......

; +$17: last scan line of the bottom-left character.
D_04DF:
        .byte   $00,$00,$00,$00,$00,$00,$00,$00; 04DF  ........

; +$1F: last scan line of the bottom-right character.  $2861-$286C stores $FF into $04C8, $04D0, $04DF
; and $04E7 when the unit's stun/dug-in byte has bit 7 set, which draws a solid 16-pixel bar across
; the top and the bottom of the cell - the dug-in marker.
D_04E7:
        .byte   $00,$00,$00,$00,$00,$00,$00,$00; 04E7  ........
        .byte   $00,$00,$00,$00,$00,$00,$00,$00; 04EF  ........
        .byte   $00,$00,$00,$00,$00,$00,$00,$00; 04F7  ........
        .byte   $00                     ; 04FF  .

; messagePtrLoTable: 36 bytes, low half of the status-message pointer table, indexed by message id
; $00-$23 (the high halves are at $0524 and the sound effect for each id at $0612).  showMessageString
; is called with A/Y taken from the two tables at $C536 (message id 0, the chat line) and $C5E5 (the
; id pulled off the 8-entry message ring at $9249); the printed message sits on screen for $3C = 60
; frames. Ids $1E, $20 and $21 are $FFFF placeholders on disk and are filled in at run time by
; setMessageSlot ($C924/$C928, X = id, A/Y = string) - for instance overlay A assembles its rules line
; from the template at $0636, copies it to $0332 and registers that copy as id $1E ($8714-$8724). The
; 36 messages: 00 the chat line ($0548), 01 WORKING..., 02 END OF FILM., 03 GAME ABORTED., 04 OPPONENT
; READY., 05 UNIT RECYCLED., 06/07/08 three SPOTTED RECYCLER! variants, 09 NOT A GAME FILM!, 0A SETUP
; UNITS. PRESS F 1 WHEN DONE., 0B WAITING FOR OPPONENT..., 0C COMCEN STUNNED!, 0D SPOTTED ENEMY
; COMCEN!, 0E SPY UNDER ATTACK!, 0F SPOTTED ENEMY SPY!, 10 WAITING FOR CONNECTION..., 11 WE LOST ON
; POINTS!, 12 WE WON ON POINTS!, 13 IT WAS A DRAW., 14 WE WERE KNOCKED OUT!, 15 WE WON BY A KNOCKOUT!,
; 16 GAME OVER., 17 PICK UP PHONE THEN PRESS SPACE., 18 PRESS SPACE, WAIT, HANGUP PHONE., 19 TIMEOUT.
; RUN/STOP TO RESUME., 1A OPPONENT PICKING GAME/MAP., 1B RESUMED, 1C THE BATTLE BEGINS!, 1D LAST QTR
; WARNING!, 1E/20/21 run-time slots, 1F OPPONENT READY FOR NEW GAME., 22 PHONE TROUBLE., 23 TRY AGAIN.
messagePtrLoTable:
        .byte   $48,$6C,$07,$76,$58,$13,$21,$2A; 0500  Hl.vX.!*  ids $00-$07: $0548 chat line, $056C WORKING..., $8707 END OF FILM., $0576 GAME ABORTED., $8858 OPPONENT READY., $8713 UNIT RECYCLED., $8721 and $872A SPOTTED RECYCLER! variants
        .byte   $31,$83,$93,$67,$42,$51,$66,$77; 0508  1..gBQfw  ids $08-$0F: $8731 SPOTTED RECYCLER!, $0583 NOT A GAME FILM!, $0593 SETUP UNITS..., $8867 WAITING FOR OPPONENT..., $8742 COMCEN STUNNED!, $8751 SPOTTED ENEMY COMCEN!, $8766 SPY UNDER ATTACK!, $8777 SPOTTED ENEMY SPY!
        .byte   $7E,$89,$9B,$AC,$BA,$CE,$E3,$97; 0510  ~.......  ids $10-$17: $887E WAITING FOR CONNECTION..., $8789 WE LOST ON POINTS!, $879B WE WON ON POINTS!, $87AC IT WAS A DRAW., $87BA WE WERE KNOCKED OUT!, $87CE WE WON BY A KNOCKOUT!, $87E3 GAME OVER., $8897 PICK UP PHONE THEN PRESS SPACE.
        .byte   $B6,$B4,$D6,$D0,$D7,$E9,$FF,$F0; 0518  ........  ids $18-$1F: $88B6 PRESS SPACE WAIT HANGUP PHONE., $05B4 TIMEOUT..., $88D6 OPPONENT PICKING GAME/MAP., $05D0 RESUMED, $05D7 THE BATTLE BEGINS!, $05E9 LAST QTR WARNING!, $FFFF run-time slot, $88F0 OPPONENT READY FOR NEW GAME.
        .byte   $FF,$FF,$FA,$08         ; 0520  ....  ids $20-$23: two more $FFFF run-time slots, then $05FA PHONE TROUBLE. and $0608 TRY AGAIN.

; messagePtrHiTable: 36 bytes, the high halves of messagePtrLoTable, same indexing.  The three page
; values show where each message lives: $05/$06 = in this file, $87 = in the resident $6F00-$87FF
; overlay (so those ids only print correctly while overlay B is in memory), $88 = in the message block
; loaded from track 29 sectors 9-10, and $FF = an unfilled run-time slot.
messagePtrHiTable:
        .byte   $05,$05,$87,$05,$88,$87,$87,$87; 0524  ........  high bytes for ids $00-$07
        .byte   $87,$05,$05,$88,$87,$87,$87,$87; 052C  ........  high bytes for ids $08-$0F
        .byte   $88,$87,$87,$87,$87,$87,$87,$88; 0534  ........  high bytes for ids $10-$17
        .byte   $88,$05,$88,$05,$05,$05,$FF,$88; 053C  ........  high bytes for ids $18-$1F - $FF at id $1E marks the empty run-time slot
        .byte   $FF,$FF,$05,$06         ; 0544  ....  high bytes for ids $20-$23 - two more empty slots, then this file's last two strings

; chatMessageLine: message id $00, the 36-byte chat line - exactly the width of the message panel.
; $0548 is a leading marker glyph: $1E while there is text to show (set at $13FD when an incoming line
; has been unpacked and at $C67E), $9E - the same glyph with bit 7 set, so the string ends immediately
; - to blank the line ($C62A).  $C5FC tests it and treats a negative value as 'no chat line'. On disk
; the whole line is 35 spaces followed by $A0 (space with the terminator bit).
chatMessageLine:
        .byte   " "                     ; 0548  marker glyph: $1E = there is a line to print, $9E = terminator, print nothing

; The 35 text bytes of the chat line, rebuilt character by character at $13EF from the raw receive
; buffer incomingChatBuffer ($9253): the newest character is stored with bit 7 set (it terminates the
; string) and the earlier ones are copied back over it as the line grows.  $0BC5 stores $9E here to
; clear the line at the start of a game.
D_0549:
        .byte   $20,$20,$20,$20,$20,$20,$20,$20; 0549          
        .byte   $20,$20,$20,$20,$20,$20,$20,$20; 0551          
        .byte   $20,$20,$20,$20,$20,$20,$20,$20; 0559          
        .byte   $20,$20,$20,$20,$20,$20,$20,$20; 0561          
        .byte   $20,$20,$A0             ; 0569    .

; msgWorking: message id $01, 'WORKING...', 10 bytes with the terminator on the final '.'; sound $80 =
; silent.  Used as the sticky message (persistentMessageId) while a disk load or an overlay swap is in
; progress ($2988, $7989, $C853).
msgWorking:
        .byte   "WORKING..",'.'|$80     ; 056C

; msgGameAborted: message id $03, 'GAME ABORTED.', 13 bytes, sound $03.  Queued at $525F when the
; opponent or the player abandons the game.
msgGameAborted:
        .byte   "GAME ABORTED"          ; 0576
        .byte   $AE                     ; 0582  the terminating '.' ($AE).  It is broken out of the string above because the data-type map is keyed by address alone and the 1541 fast loader has a data block at its own $0582

; msgNotAGameFilm: message id $09, 'NOT A GAME FILM!', 16 bytes, sound $03.  Queued at $81F9 when the
; block just loaded from a save slot is not a game film.
msgNotAGameFilm:
        .byte   "NOT A GAME FILM",'!'|$80; 0583

; msgSetupUnitsPressF1WhenDone: message id $0A, 'SETUP UNITS. PRESS F 1 WHEN DONE.', 33 bytes, silent
; ($80).  Queued at $1FF2 when the pre-battle setup phase starts; F1 (screen 0) ends it.
msgSetupUnitsPressF1WhenDone:
        .byte   "SETUP UNITS. PRESS F 1 WHEN DONE",'.'|$80; 0593

; msgTimeoutRunStopToResume: message id $19, 'TIMEOUT. RUN/STOP TO RESUME.', 28 bytes, sound $19.
; Shown as a sticky message ($5287, $C72B) when the opponent has not answered for too long; RUN/STOP
; restarts the exchange.
msgTimeoutRunStopToResume:
        .byte   "TIMEOUT. RUN/STOP TO RESUME",'.'|$80; 05B4

; msgResumed: message id $1B, 'RESUMED', 7 bytes, sound $19.  Queued at $52A6 when the link comes back
; after a timeout or a pause.
msgResumed:
        .byte   "RESUME",'D'|$80        ; 05D0

; msgTheBattleBegins: message id $1C, 'THE BATTLE BEGINS!', 18 bytes, sound $03.  Queued at $0B72 when
; the setup phase ends and the clock starts.
msgTheBattleBegins:
        .byte   "THE BATTLE "           ; 05D7
        .byte   $42                     ; 05E2  'BEGINS!' - the tail of the string above, broken out because the 1541 fast loader has a data block at its own $05E2 and data types are keyed by address alone
        .byte   $45,$47,$49,$4E,$53,$A1 ; 05E3  EGINS.

; msgLastQtrWarning: message id $1D, 'LAST QTR WARNING!', 17 bytes, sound $19.  Queued at $4621 when
; the game clock enters the last of its four quarters.
msgLastQtrWarning:
        .byte   "LAST QTR WA"           ; 05E9
        .byte   "RNING",'!'|$80         ; 05F4  'RNING!' - the tail of the string above, broken out the same way by the fast loader's hiddenSignature at its own $05F4

; msgPhoneTrouble: message id $22, 'PHONE TROUBLE.', 14 bytes, sound $15.  Shown at $5A50 when the
; modem link fails.
msgPhoneTrouble:
        .byte   "PHONE TROUBLE",'.'|$80 ; 05FA

; msgTryAgain: message id $23, 'TRY AGAIN.', 10 bytes, sound $03.  Shown at $5A32, usually right after
; PHONE TROUBLE.
msgTryAgain:
        .byte   "TRY AGAIN",'.'|$80     ; 0608

; messageSoundTable: 36 bytes, the sound effect played when message id X is put on screen.
; $C543-$C55E loads it with Y = the message id and passes it to playSound, except when bit 7 is set
; ($80 = silent). The sounds group the messages: $03 neutral notice, $04 spotted/alert, $06 the two
; phone prompts,
; $0D we won, $0E we lost, $15 phone trouble, $19 clock/status (GAME OVER, TIMEOUT, RESUMED, LAST
; QTR).
messageSoundTable:
        .byte   $80,$80,$03,$03,$03,$04,$03,$03; 0612  ........  ids $00-$07: chat and WORKING... silent; END OF FILM / GAME ABORTED / OPPONENT READY $03; UNIT RECYCLED $04; the recycler sightings $03
        .byte   $04,$03,$80,$03,$04,$04,$04,$04; 061A  ........  ids $08-$0F: SPOTTED RECYCLER $04, NOT A GAME FILM $03, SETUP UNITS silent, WAITING FOR OPPONENT $03, then $04 for COMCEN STUNNED, ENEMY COMCEN, SPY UNDER ATTACK and ENEMY SPY
        .byte   $80,$0E,$0D,$80,$0E,$0D,$19,$06; 0622  ........  ids $10-$17: WAITING FOR CONNECTION silent, lost $0E, won $0D, draw silent, knocked out $0E, knockout win $0D, GAME OVER $19, PICK UP PHONE $06
        .byte   $06,$19,$03,$19,$03,$19,$03,$80; 062A  ........  ids $18-$1F: PRESS SPACE $06, TIMEOUT $19, OPPONENT PICKING $03, RESUMED $19, THE BATTLE BEGINS $03, LAST QTR WARNING $19, run-time slot $03, OPPONENT READY FOR NEW GAME silent
        .byte   $80,$03,$15,$03         ; 0632  ....  ids $20-$23: run-time slot silent, run-time slot $03, PHONE TROUBLE $15, TRY AGAIN $03

; optionSummaryTemplate: 23 bytes, ' MENUS XXXX DAMAGE XXX.' with the terminator on the '.'.  It is a
; fill-in-the-blanks rules line, not a constant string: appendOnOffWord ($872E in overlay A) copies
; one of the words from optionWordTable over the XXXX field at offset 7 and the XXX field at offset
; $13, writes '.'|$80 straight after the last word it substituted and adds ',' / ', ' padding, then
; $8714 copies the finished line to $0332 and $8722 registers that copy as message id $1E. Offsets: 0
; space, 1-6 'MENUS ', 7-10 the OFF/ON field, 11 space, 12-17 'DAMAGE', 18 space, 19-21 the
; FULL/HALF/NONE field, 22 '.'.
optionSummaryTemplate:
        .byte   " MENUS XXXX DAMAGE XXX",'.'|$80; 0636

; optionWordTable: 17 bytes, five bit-7 terminated words at fixed offsets: +$00 'OFF' ($064D), +$03
; 'ON' ($0650), +$05 'FULL' ($0652), +$09 'HALF' ($0656), +$0D 'NONE' ($065A). Only the first two are
; used.  appendOnOffWord ($8734) is the single reader of the table in the whole program and it enters
; with Y = 0 (OFF) or Y = 3 (ON), for both fields of optionSummaryTemplate, and
; $8632 prints 'OFF' and then - through printStringAtPointer, which carries on from where the previous
; string ended - the 'ON' behind it, as the two items of a menu.  FULL, HALF and NONE have no reader
; at all: they look like leftovers from a damage option that ended up being just on/off. (XREF.txt
; shows this address as eaCopyrightMessage; that is the 1541 drive's own $064D, a different address
; space entirely.)
optionWordTable:
        .byte   "OF",'F'|$80,"O",'N'|$80,"FUL",'L'|$80,"HAL",'F'|$80,"NON"; 064D
        .byte   'E'|$80                 ; 064D

; msgClicks: 10 bytes, '   CLICKS:' - the header of the score line drawn under the map.  Clicks are
; Modem Wars' score unit.  $C6BC prints it (A = $5E, Y = $06), then the game clock, then the next two
; strings in memory through printStringAtPointer, each followed by a 16-bit score from
; sideScoreLo/sideScoreHi ($92A2/$92A4).
msgClicks:
        .byte   "   CLICKS",':'|$80     ; 065E

; msgUs: 7 bytes, '    US:'.  Printed straight after the clock by the printStringAtPointer at $C6CB,
; which continues from wherever the previous string ended - which is why these three labels have to
; stay adjacent and in this order.  The number that follows it comes from sideScore indexed by
; playerSide EOR 1 ($C6CE), so the score arrays look as if they are indexed by the side that conceded
; the points.
msgUs:
        .byte   "    US",':'|$80        ; 0668

; msgThem: 8 bytes, '   THEM:', printed by the same routine at $C6D6 and followed by the score indexed
; by playerSide itself.
msgThem:
        .byte   "   THEM",':'|$80       ; 066F

; msgTypeMapName: 40 bytes, three lines separated by $0D: 'TYPE MAP NAME' / ' (F7 RANDOM)' / ' (F5
; SYM/RND)'.  Printed by the map chooser at $76C9 (A = $77, Y = $06) while the player types the
; base-32 name of the battlefield both sides have agreed on, or picks a random / symmetrical-random
; one. (The manual look-up check at $09EF prints its own prompt from $0A47, not this one.)
msgTypeMapName:
        .byte   "TYPE MAP NAME",$0D," (F7 RANDOM)",$0D," (F5 SYM/RND"; 0677
        .byte   ')'|$80                 ; 0677

; msgBtnToAccept: 27 bytes, two lines separated by $0D: 'BTN TO ACCEPT' / '(F 1 TO FLIP)'.  Printed at
; $76D9 while the generated map is on screen; the fire button accepts it and F1 shows it rotated 180
; degrees (mapFlipFlag $0B9D).
msgBtnToAccept:
        .byte   "BTN TO ACCEPT",$0D,"(F 1 TO FLIP",')'|$80; 069F

; ----------------------------------------------------------------------
; switchToRequestedScreen - the game's screen dispatcher, called once per iteration from every
; screen's main loop to honour a pending F1/F3/F5/F7 keypress.  It returns at once while the chat
; editor has the keyboard, while no function key is pending, or when the key names the screen that is
; already showing; otherwise it makes the request current, throws the caller's stack away, turns every
; sprite off, rebuilds the bitmap display and jumps into the new screen's entry point - so it never
; returns in that case.
; In:  zp_66 requestedScreenKey ($00 F1, $01 F3, $02 F5, $03 F7, $FF or a typed character = no
;     request), $90FB currentScreen, $929C chatState, $0B9B bit 7 filmPlaybackMode
; Out: either RTS with nothing changed, or: $90FB updated, sprites and their shadows ($9020, $9022,
;     $9023, $9024) cleared, $92F9 cleared unless a film is replaying, S = $FD, and a JMP to
;     enterMapScreen ($1FC3, screen 0, with $9020 preset to $C4), runMissileScreen ($7587, screen 1),
;     enterDroneScreen ($7FE6, screen 2) or enterMainMenu ($141D, anything else)
; Called from: the console loop $1463, the battlefield loop $2033 and, in the comcen-screens overlay,
;     the missile screen $75C3 and the drone screen $8040.  Screens 1 and 2 live in that overlay, so
;     they can only be reached while it is resident.  (XREF.txt also lists the map generator as
;     calling $75C3 and $8040; that is an artefact of the two $6F00-$87FF overlays sharing one address
;     range - in the map generator those two addresses hold unrelated code.)
; ----------------------------------------------------------------------
switchToRequestedScreen:
        lda     chatState               ; 06BA  $929C is non-zero while the chat line editor owns the keyboard
        bne     L_06C8                  ; 06BD  refuse to switch in the middle of typing a message
        lda     requestedScreenKey      ; 06BF  zp_66 = the last key seen by the IRQ poll; the four function keys decode to 0-3 in keyMatrixCodeTable, everything else has bit 7 set
        bmi     L_06C8                  ; 06C1  nothing pending: $FF (no key) and any typed character ($80-$FF) are both negative
        cmp     currentScreen           ; 06C3  $90FB = the screen already on show: 0 battlefield map, 1 comcen missile, 2 drone, 3 console/main menu
        bne     L_06C9                  ; 06C6  a different screen was asked for - go and switch
L_06C8:
        rts                             ; 06C8  same screen or nothing to do: back into the caller's main loop

L_06C9:
        sta     currentScreen           ; 06C9  the requested screen becomes the current one
        ldx     #$FD                    ; 06CC  $FD is the game's 'empty stack' value ...
        txs                             ; 06CE  ... reset it, because this routine jumps into the new screen instead of returning (mainStart does the same at $0AAC)
        lda     #$00                    ; 06CF  zero for all the sprite registers below
        sta     spriteEnableShadow      ; 06D1  $9022 = the shadow of $D015 that the IRQ copies out every frame
        sta     VIC_SPR_ENA             ; 06D4  and switch all eight sprites off immediately as well
        sta     spriteXMsbShadow        ; 06D7  $9020 = shadow of $D010, the sprite X bit 8 flags
        sta     spriteXExpandShadow     ; 06DA  $9023 = shadow of $D01D, sprite X expansion
        sta     spriteYExpandShadow     ; 06DD  $9024 = shadow of $D017, sprite Y expansion
        bit     filmPlaybackMode        ; 06E0  $0B9B bit 7 = a game film is being replayed
        bmi     L_06E8                  ; 06E3  during a replay leave the drone flags alone - the recorded command stream has to play back exactly as it was taken down
        sta     droneArrivalFlags       ; 06E5  clear the drone handshake flags $92F9 (bit 0 = a 'drone finished' command $92 is already queued, bits 4-5 = the launch/arrival states) so the new screen starts clean
L_06E8:
        jsr     initBitmapScreen        ; 06E8  rebuild the whole bitmap display: VIC registers, video matrix, colour RAM and the window frames
        lda     currentScreen           ; 06EB  dispatch on the new screen number
        bne     L_06F8                  ; 06EE  not the battlefield map
        lda     #$C4                    ; 06F0  $C4 = %11000100: preset the X bit-8 flag for sprites 2, 6 and 7 - the map screen's sprites that live in the right-hand half of the display
        sta     spriteXMsbShadow        ; 06F2  into the $D010 shadow, before the map screen puts its sprites up
        jmp     enterMapScreen          ; 06F5  screen 0 (F1): the battlefield map at $1FC3

L_06F8:
        cmp     #$02                    ; 06F8  screen 2?
        bne     L_06FF                  ; 06FA  no
        jmp     enterDroneScreen        ; 06FC  screen 2 (F5): the drone screen at $7FE6, in the comcen-screens overlay

L_06FF:
        cmp     #$01                    ; 06FF  screen 1?
        bne     L_0706                  ; 0701  no - then it can only be 3
        jmp     runMissileScreen        ; 0703  screen 1 (F3): the comcen missile screen at $7587, in the comcen-screens overlay

L_0706:
        jmp     enterMainMenu           ; 0706  screen 3 (F7): the console with its STATS / REPAIR / MISC panels at $141D


; ----------------------------------------------------------------------
; runComcenStunnedBlackout - the 'COMCEN STUNNED!' cut-scene: when your command centre has been
; knocked out, control is taken away, the screen goes blank and the engine keeps running unwatched for
; exactly one full round of all 100 units before the game hands control back.  It releases the drone,
; clears the screen, resets the stack, then loops on updateGameFrame with the input lockout re-armed
; and bit 6 of comcenStunStatus set, which makes the sprite enabler at $31A6 and the cell plotter at
; $5C7C give up so that nothing is drawn at all, until unitOrderIndex comes back to the value it had
; on entry; then it queues message $0C, unlocks the input, clears bits 4-6 and re-enters whichever
; screen was showing.
; In:  $9162 unitOrderIndex (captured into the CMP operand at $073E), $922A comcenStunStatus, $90FB
;     currentScreen, $92C9 gameEndCountdown and $0BA8 gameEndReason (abort conditions), $90FC
;     stepDelayTimer
; Out: one blind engine round; $0B7D re-armed each step and cleared at the end; $922A left at bit 7 +
;     countdown; message $0C queued through $C566; never returns - it jumps to runMapMainLoop ($1FD0,
;     after redrawing the map and viewport), missileScreenResume ($759C), resetDroneScreen ($7FF6) or
;     enterMainMenuKeepSelection ($1414)
; Called from: JMP at $4546 only, taken after a unit step when comcenStunStatus has reached $FF
;     (written by $730D when the missile hits the comcen) and no film is being replayed.  During a
;     replay the cut-scene is skipped and the overlay queues the message on its own at $72F6 instead.
; ----------------------------------------------------------------------
runComcenStunnedBlackout:
        jsr     releaseDroneControl     ; 0709  if a drone is still in the air, hand it back ($8230 in the comcen-screens overlay) - the player is about to lose control of everything
        jsr     clearMessageLineAndScreen; 070C  wipe the message line and the screen windows: this is the blackout
        ldx     #$FD                    ; 070F  this routine never returns to the caller of $4546 ...
        txs                             ; 0711  ... so reset the stack to the game's empty value
        lda     unitOrderIndex          ; 0712  $9162 = how far the round's 100-unit process order has got; $4537 has just bumped it
        sta     L_073D+1                ; 0715  self-modifying: this is the operand byte of the CMP at $073D ($073E), so the loop below ends when the round order comes back round to this same unit - exactly one full pass of all 100 units
        lda     #$CF                    ; 0718  $CF = %11001111: keep bit 7 (comcen stunned), set bit 6 (run blind) and load the low nibble with a full $F stun countdown.  Just as important, it is no longer $FF, so the test at $453A cannot start this cut-scene again on the next unit step
        sta     comcenStunStatus        ; 071A  arm the blackout
L_071D:
        lda     comcenStunStatus        ; 071D  top of the blackout loop: re-read the status byte, because updateGameFrame may have changed it
        ora     #$40                    ; 0720  bit 6 suppresses all drawing: the sprite enabler at $31A6 and the viewport plotter at $5C7C both give up when it is set, and updateGameFrame itself skips the input handling at $40BA
        sta     comcenStunStatus        ; 0722  store it back
        sta     inputLockoutTimer       ; 0725  the same $CF value doubles as the input lockout count: the IRQ decrements $0B7D every frame and ignores the keyboard and joystick while it is non-zero.  It is re-armed on every step, so the player stays locked out for the whole round
L_0728:
        lda     gameEndCountdown        ; 0728  $92C9 counts down the end-of-game sequence; non-zero means the game is already over
        bne     L_0746                  ; 072B  the game is ending - stop the blackout now
        lda     gameEndReason           ; 072D  $0BA8 = 0 while a game is running (1 aborted, $08 time up, $A0/$C0 a comcen was knocked out)
        bne     L_0746                  ; 0730  the game has ended - stop
        lda     stepDelayTimer          ; 0732  $90FC = frames still to wait before the engine may take its next unit step
        bne     L_0728                  ; 0735  not yet: spin here, re-testing both abort conditions while the IRQ ticks the timer down
        jsr     updateGameFrame         ; 0737  one engine step: one unit is processed, with the input locked out and nothing drawn
        lda     unitOrderIndex          ; 073A  where has the round order got to now?
L_073D:
        cmp     #$FF                    ; 073D  the immediate operand was patched at $0715 with the index the round was at when the comcen went down
        bne     L_071D                  ; 073F  keep stepping until it comes round again - the index runs 0..99 and is reset to 0 at $474D when the round finishes
        lda     #$0C                    ; 0741  message id $0C = 'COMCEN STUNNED!' (the string lives at $8742 in the comcen-screens overlay; sound $04)
        jsr     queueMessage            ; 0743  put it in the 8-entry message ring at $9249
L_0746:
        lda     #$00                    ; 0746  0 = no lockout ...
        sta     inputLockoutTimer       ; 0748  ... give the keyboard and the joystick back to the player
        lda     comcenStunStatus        ; 074B  and stop running blind:
        and     #$8F                    ; 074E  clear bits 4-6 (bit 6 was the blackout), keeping bit 7 'comcen stunned' and the low-nibble countdown that the console's REPAIR panel shows
        sta     comcenStunStatus        ; 0750  store the tidied status byte
        ldy     currentScreen           ; 0753  return to whichever screen was showing when the comcen was hit
        bne     L_0761                  ; 0756  not the battlefield map
        jsr     drawOverviewMap         ; 0758  screen 0: redraw the 40x40 overview panel that the blackout wiped ...
        jsr     drawViewport            ; 075B  ... and the 7x5-cell tactical viewport
        jmp     runMapMainLoop          ; 075E  back into the map's main loop, not its entry point - the screen has just been set up by hand

L_0761:
        dey                             ; 0761  screen 1?
        bne     L_0767                  ; 0762  no
        jmp     missileScreenResume     ; 0764  the missile screen has its own re-entry point for exactly this case ($759C)

L_0767:
        dey                             ; 0767  screen 2?
        bne     L_076D                  ; 0768  no - then it is the console
        jmp     resetDroneScreen        ; 076A  re-initialise the drone panel; the drone itself was released at $0709

L_076D:
        jmp     resumeMainMenuScreen    ; 076D  screen 3: back to the console, keeping the menu item that was selected


; tileVariantMaskTable: 40 bytes ($0770-$0797), indexed by map cell value - $40, and by cell - $20 for
; cells below $40 (randomTileVariant $7872 subtracts $40 and adds $20 back if that went negative), so
; the first 32 entries serve both the $20-$3F and the $40-$5F cell ranges and the last 8 serve
; $60-$67. The byte is a bit mask: the map generator ANDs a fresh scenario random with it and adds the
; result to the base tile, so a mask of 0 means 'always this exact tile', 1 means 'one of two' and 3
; means 'one of four'.  Only eight entries are non-zero: cells $49, $4B and $65 (and $29, $2B) get
; mask 1, cells $4D,
; $51, $59, $5D, $61 (and $2D, $31, $39, $3D) get mask 3.
tileVariantMaskTable:
        .byte   $00,$00,$00,$00,$00,$00,$00,$00; 0770  ........  cells $40-$4D (and $20-$2D): all fixed except $49 and $4B (mask 1) and $4D (mask 3)
        .byte   $00,$01,$00,$01,$00,$03 ; 0778  ......

; $077E is also the base address of terrainClassTable below: everything that wants the terrain class
; of a
; cell reads $077E + cell value, so the class bytes really start at $077E+$21 = $079F.  The 26 bytes
; here are still tile variant masks (cells $4E-$67), which is why the two tables have to sit exactly
; this far apart.
D_077E:
        .byte   $00,$00,$00,$03,$00,$00,$00,$00; 077E  ........  cells $4E-$67 (and $2E-$3F): mask 3 at $51, $59, $5D and $61, mask 1 at $65, everything else fixed
        .byte   $00,$00,$00,$03,$00,$00,$00,$03; 0786  ........
        .byte   $00,$00,$00,$03,$00,$00,$00,$01; 078E  ........
        .byte   $00,$00                 ; 0796  ..

; tileVariantPad: the single $00 that pads the variant masks out to the glyph table.  As terrain class
; data it would be the class of cell $1A, which never occurs.
tileVariantPad:
        .byte   $00                     ; 0798  .  $00 pad; read as terrain class data through the $077E base this would be the class of cell $1A, which never occurs

; unitGlyphBaseTable: 5 bytes plus a pad, the base glyph number of the 16x16 unit picture for each
; unit type: $2C GRUNT, $30 RIDER, $34 BOOMER, $38 SPY, $3C COMCEN.  The facing (unit flags AND 3) is
; added to it, then $2C is subtracted and the result doubled to index unitGlyphPointerTable, i.e.
; glyph = type*4 + facing ($289A-$28A8 for the tactical view, $7512 and $7538 for the radar screen,
; where the enemy version has bit 7 set). The trailing $00 at $079E is both the pad and, read as
; terrain class data through the $077E base, the class of cell $20 - class 0, blank ground.
unitGlyphBaseTable:
        .byte   $2C,$30,$34,$38,$3C,$00 ; 0799  ,048<.  $2C GRUNT, $30 RIDER, $34 BOOMER, $38 SPY, $3C COMCEN, then the $00 pad at $079E

; terrainClassTable: map cell value -> terrain class 0-8, 75 bytes covering cells $21-$6B.  Every
; reader addresses it as $077E + cell (see the note there); $5BC8 gets to the same bytes as $07BE +
; (cell-$40) after clamping the index below $21. The class is what the rest of the engine works with:
; terrainMoveCostTable $405E (move points charged per step: 20, 65, 30, 40, 20, 40, 20, 20),
; terrainHeightTable $38B3 (0, 0, 0, 1, 2, 3, 4, 0 - line of sight and the combat height bonus),
; terrainSightBonusTable $35BB (0, 0, 0, 1, 1, 2, 2, 0 - extra range, doubled when spotting) and
; terrainColourTable $07EA are all indexed by it.  Class 2 (woods) is also tested by name at $36B5 and
; $3729 because it blocks line of sight.  Note that those four engine tables only have eight entries,
; so class 8 - the off-map border - runs off the end of them; nothing ever walks or shoots there.
terrainClassTable:
        .byte   $03,$03,$03,$03,$03,$03,$03,$03; 079F  ........  cells $21-$34 -> class 3, the lower contour band: costs 40 move points, height 1, +1 sight
        .byte   $03,$03,$03,$03,$03,$03,$03,$03; 07A7  ........
        .byte   $03,$03,$03,$03         ; 07AF  ....
        .byte   $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF; 07B3  ........  cells $35-$3F -> $FF, an invalid class: these cell values are never put on the map
        .byte   $FF,$FF,$FF             ; 07BB  ...
D_07BE:
        .byte   $00                     ; 07BE  .  cell $40 -> class 0, plain open ground: 20 move points, no height, no sight bonus, drawn yellow
        .byte   $05,$05,$05,$05,$05,$05,$05,$05; 07BF  ........  cells $41-$54 -> class 5, the upper contour band: 40 move points, height 3, +2 sight
        .byte   $05,$05,$05,$05,$05,$05,$05,$05; 07C7  ........
        .byte   $05,$05,$05,$05         ; 07CF  ....
        .byte   $01,$01,$01,$01,$01,$01,$01,$01; 07D3  ........  cells $55-$5C -> class 1, river: 65 move points, the most expensive terrain in the game, drawn cyan
        .byte   $02,$02,$02,$02         ; 07DB  ....  cells $5D-$60 -> class 2, woods: 30 move points and the only terrain that blocks line of sight, drawn green
        .byte   $04,$04,$04,$04         ; 07DF  ....  cells $61-$64 -> class 4, hill: 20 move points, height 2, +1 sight
        .byte   $06,$06                 ; 07E3  ..  cells $65-$66 -> class 6, summit: 20 move points, height 4 (the best view in the game), +2 sight
        .byte   $07                     ; 07E5  .  cell $67 -> class 7, the recycler pad where damaged units are repaired ($3F27 stamps $67 into the map)
        .byte   $08,$08,$08,$08         ; 07E6  ....  cells $68-$6B -> class 8, the off-map border: $278A builds these four values as $68 + ((row+col) AND 3) for any cell outside the 40x40 battlefield

; terrainColourTable: 9 bytes, the colour-RAM nibble for terrain class 0-8, read at $27AB (viewport)
; and
; $5BCB (overview map) straight after a class look-up: 7 yellow open ground, 3 cyan river, 5 green
; woods,
; 8 orange for the contour bands, hills and summits, 0 black for the recycler pad and the off-map
; border.
terrainColourTable:
        .byte   $07,$03,$05,$08,$08,$08,$08,$00; 07EA  ........  class 0-8: $07 yellow, $03 cyan, $05 green, $08 orange x4, $00 black x2
        .byte   $00                     ; 07F2  .

; table0400Tail: 13 bytes of $00 filler to the end of the page.  The block move that installed the
; page copies four whole pages, so this is simply whatever followed the tables in the source file.
table0400Tail:
        .byte   $00,$00,$00,$00,$00,$00,$00,$00; 07F3  ........  unused filler
        .byte   $00,$00,$00,$00,$00     ; 07FB  .....
