; ****************************************************************************** ; ; COMMODORE 64 ELITE MAIN GAME SOURCE ; ; Commodore 64 Elite was written by Ian Bell and David Braben and is copyright ; D. Braben and I. Bell 1985 ; ; The code in this file is identical to the source disks released on Ian Bell's ; personal website at http://www.elitehomepage.org/ (it's just been reformatted ; to be more readable) ; ; The commentary is copyright Mark Moxon, and any misunderstandings or mistakes ; in the documentation are entirely my fault ; ; The terminology and notations used in this commentary are explained at ; https://elite.bbcelite.com/terminology ; ; The deep dive articles referred to in this commentary can be found at ; https://elite.bbcelite.com/deep_dives ; ; ------------------------------------------------------------------------------ ; ; This source file contains the main game code for Commodore 64 Elite. ; ; ------------------------------------------------------------------------------ ; ; This source file produces the following binary files: ; ; * ELTA.bin ; * ELTB.bin ; * ELTC.bin ; * ELTD.bin ; * ELTE.bin ; * ELTF.bin ; * ELTG.bin ; * ELTH.bin ; * ELTI.bin ; * ELTJ.bin ; * ELTK.bin ; ; ****************************************************************************** INCLUDE "1-source-files/main-sources/elite-build-options.asm" _GMA85_NTSC = (_VARIANT = 1) _GMA86_PAL = (_VARIANT = 2) _GMA_RELEASE = (_VARIANT = 1) OR (_VARIANT = 2) _SOURCE_DISK_BUILD = (_VARIANT = 3) _SOURCE_DISK_FILES = (_VARIANT = 4) _SOURCE_DISK = (_VARIANT = 3) OR (_VARIANT = 4) ; ****************************************************************************** ; ; Configuration variables ; ; ****************************************************************************** CODE% = $1D00 ; The address where the first block of game code will ; be run (ELITE A to C) LOAD% = $1D00 ; The address where the first block of game code will ; be loaded (ELITE A to C) IF _GMA_RELEASE C% = $6A00 ; The address where the second block of game code will ; be loaded and run (ELITE D onwards) ELIF _SOURCE_DISK C% = $7300 ; The address where the second block of game code will ; be loaded and run (ELITE D onwards) ENDIF Q% = _MAX_COMMANDER ; Set Q% to TRUE to max out the default commander, FALSE ; for the standard default commander USA% = TRUE ; Set USA% to FALSE to introduce a timing loop to bring ; PAL machines in line with NTSC machines (interestingly ; both the GMA85 NTSC and GMA86 PAL versions have this ; set to TRUE, so the timing loop is not included in ; either version) NOST = 12 ; The number of stardust particles in normal space (this ; goes down to 3 in witchspace) NOSH = 10 ; The maximum number of ships in our local bubble of ; universe NTY = 33 ; The number of different ship types MSL = 1 ; Ship type for a missile SST = 2 ; Ship type for a Coriolis space station ESC = 3 ; Ship type for an escape pod PLT = 4 ; Ship type for an alloy plate OIL = 5 ; Ship type for a cargo canister AST = 7 ; Ship type for an asteroid SPL = 8 ; Ship type for a splinter SHU = 9 ; Ship type for a Shuttle CYL = 11 ; Ship type for a Cobra Mk III ANA = 14 ; Ship type for an Anaconda HER = 15 ; Ship type for a rock hermit (asteroid) COPS = 16 ; Ship type for a Viper SH3 = 17 ; Ship type for a Sidewinder KRA = 19 ; Ship type for a Krait ADA = 20 ; Ship type for an Adder WRM = 23 ; Ship type for a Worm CYL2 = 24 ; Ship type for a Cobra Mk III (pirate) ASP = 25 ; Ship type for an Asp Mk II THG = 29 ; Ship type for a Thargoid TGL = 30 ; Ship type for a Thargon CON = 31 ; Ship type for a Constrictor COU = 32 ; Ship type for a Cougar DOD = 33 ; Ship type for a Dodecahedron ("Dodo") space station JL = ESC ; Junk is defined as starting from the escape pod JH = SHU+2 ; Junk is defined as ending before the Cobra Mk III ; ; So junk is defined as the following: escape pod, ; alloy plate, cargo canister, asteroid, splinter, ; Shuttle or Transporter PACK = SH3 ; The first of the eight pack-hunter ships, which tend ; to spawn in groups. With the default value of PACK the ; pack-hunters are the Sidewinder, Mamba, Krait, Adder, ; Gecko, Cobra Mk I, Worm and Cobra Mk III (pirate) POW = 15 ; Pulse laser power Mlas = 50 ; Mining laser power Armlas = INT(128.5 + 1.5*POW) ; Military laser power NI% = 37 ; The number of bytes in each ship's data block (as ; stored in INWK and K%) X = 128 ; The centre x-coordinate of the 256 x 144 space view Y = 72 ; The centre y-coordinate of the 256 x 144 space view conhieght = 80 ; The size of the gap left for the rotating Constrictor ; at the top of the briefing for mission 1 f0 = $3C ; Internal key number for key F1 (Launch, Front) f1 = $08 ; Internal key number for key "1" (Buy Cargo) f2 = $05 ; Internal key number for key "2" (Sell Cargo) f3 = $38 ; Internal key number for key "3" (Equip Ship) f4 = $35 ; Internal key number for key "4" (Long-range Chart) f5 = $30 ; Internal key number for key "5" (Short-range Chart) f6 = $2D ; Internal key number for key "6" (Data on System) f7 = $28 ; Internal key number for key "7" (Market Price) f8 = $25 ; Internal key number for key "8" (Status Mode) f9 = $20 ; Internal key number for key "9" (Inventory) f12 = $3B ; Internal key number for key F3 (Rear) f22 = $3A ; Internal key number for key F5 (Left) f32 = $3D ; Internal key number for key F7 (Right) DINT = $2E ; Internal key number for key "D" (Distance to system) FINT = $2B ; Internal key number for key "F" (System search) HINT = $23 ; Internal key number for key "H" (Hyperspace) OINT = $1A ; Internal key number for key "O" (Crosshairs home) YINT = $27 ; Internal key number for key "Y" (Y/N) RED = %01010101 ; Four multicolour bitmap mode pixels of colour %01, ; which is mapped to the danger colour for the dashboard ; dials, or red on the scanner, via the colour mapping ; in sdump (high nibble) YELLOW = %10101010 ; Four multicolour bitmap mode pixels of colour %10, ; which is mapped to the normal indicator colour for ; the dashboard dials, or yellow on the scanner, via the ; colour mapping in sdump (low nibble) GREEN = %11111111 ; Four multicolour bitmap mode pixels of colour %11, ; which is mostly mapped to green for the notched lines ; on the dashboard, or light green on the scanner, via ; the colour mapping in cdump (low nibble) WHITE = %01011010 ; Four multicolour bitmap mode pixels of colours %01, ; %01, %10 and %10, for showing Thargoids on the scanner ; with a striped design of red and yellow BLUE = YELLOW ; Ships that are set to a scanner colour of BLUE in the ; scacol table will actually be shown in yellow CYAN = YELLOW ; Ships that are set to a scanner colour of CYAN in the ; scacol table will actually be shown in yellow MAG = YELLOW ; Ships that are set to a scanner colour of MAG in the ; scacol table will actually be shown in yellow RED2 = $27 ; A multicolour bitmap mode palette byte for screen RAM ; that sets red (2) for %01 in the bitmap and yellow (7) ; for %10 in the bitmap, for displaying a red missile ; indicator GREEN2 = $57 ; A multicolour bitmap mode palette byte for screen RAM ; that sets green (5) for %01 in the bitmap and yellow ; (7) for %10 in the bitmap, for displaying a green ; missile indicator YELLOW2 = $87 ; A multicolour bitmap mode palette byte for screen RAM ; that sets orange (8) for %01 in the bitmap and yellow ; (7) for %10 in the bitmap, for displaying an orange ; missile indicator BLACK2 = $B7 ; A multicolour bitmap mode palette byte for screen RAM ; that sets dark grey ($B) for %01 in the bitmap and ; yellow (7) for %10 in the bitmap, for displaying an ; empty missile indicator MAG2 = $40 ; A multicolour text mode palette byte for screen RAM ; that displays purple (4) foreground text on a black ; (0) background for showing player input in the text ; view BULBCOL = $E0 ; A multicolour bitmap mode palette byte that is EOR'd ; into screen RAM to toggle the E.C.M. and space station ; indicator bulbs in light blue ($E), as the "E" and "S" ; are in colour %01, and this is initially set to black ; in the colour mapping in sdump sfxplas = 0 ; Sound 0 = Pulse lasers fired by us sfxelas = 1 ; Sound 1 = Being hit by lasers 1 sfxhit = 2 ; Sound 2 = Other ship exploding sfxexpl = 3 ; Sound 3 = We died / Collision sfxwhosh = 4 ; Sound 4 = Missile launched / Ship launch sfxbeep = 5 ; Sound 5 = Short, high beep sfxboop = 6 ; Sound 6 = Long, low beep sfxhyp1 = 7 ; Sound 7 = Hyperspace drive engaged 1 sfxeng = 8 ; Sound 8 = This sound is not used sfxecm = 9 ; Sound 9 = E.C.M. on sfxblas = 10 ; Sound 10 = Beam lasers fired by us sfxalas = 11 ; Sound 11 = Military lasers fired by us sfxmlas = 12 ; Sound 12 = Mining lasers fired by us sfxbomb = 13 ; Sound 13 = Energy bomb sfxtrib = 14 ; Sound 14 = Trumbles dying sfxelas2 = 15 ; Sound 15 = Being hit by lasers 2 NRU% = 0 ; The number of planetary systems with extended system ; description overrides in the RUTOK table ; ; NRU% is set to 0 in the original source, but this is a ; bug, as it should match the number of entries in the ; RUGAL table ; ; This bug causes the Data on System screen to crash the ; game for a small number of systems - for example, the ; game will freeze if you bring up the Data on System ; screen after docking at Biarge in the first galaxy ; during the Constrictor mission RE = $23 ; The obfuscation byte used to hide the recursive tokens ; table from crackers viewing the binary code VE = $57 ; The obfuscation byte used to hide the extended tokens ; table from crackers viewing the binary code KEY1 = $36 ; The seed for encrypting LOCODE from G% to R%, where ; LOCODE = ELTA-C KEY2 = $49 ; The seed for encrypting HICODE from R% to F%, where ; HICODE = ELTD-K LL = 30 ; The length of lines (in characters) of justified text ; in the extended tokens system PALCK = LO(311) ; When USA% is set to FALSE, a timing loop is included ; in the build that waits until the raster line in PALCK ; is reached; the LO() extracts the lower byte of the ; raster line to make it easier to compare with VIC-II ; register $12, which contains the bottom byte of the ; 9-bit raster line count l1 = $0001 ; The 6510 input/output port register, which we can use ; to configure the Commodore 64 memory layout (see page ; 260 of the Programmer's Reference Guide) BRKV = $0316 ; The CBINV break vector that we intercept to enable us ; to handle and display system errors NMIV = $0318 ; The NMINV vector that we intercept with our custom NMI ; handler, which just acknowledges NMI interrupts and ; ignores them CHRV = $0326 ; The IBSOUT vector that we intercept with our custom ; text printing routine QQ18 = $0700 ; The address of the text token table, as set in ; elite-data.asm SNE = $0AC0 ; The address of the sine lookup table, as set in ; elite-data.asm ACT = $0AE0 ; The address of the arctan lookup table, as set in ; elite-data.asm FONT = $0B00 ; The address of the game's text font TKN1 = $0E00 ; The address of the extended token table, as set in ; elite-data.asm RUPLA = TKN1 + $C28 ; The address of the extended system description system ; number table, as set in elite-data.asm RUGAL = TKN1 + $C42 ; The address of the extended system description galaxy ; number table, as set in elite-data.asm RUTOK = TKN1 + $C5C ; The address of the extended system description token ; table, as set in elite-data.asm SCBASE = $4000 ; The address of the screen bitmap DLOC% = SCBASE+18*8*40 ; The address in the screen bitmap of the start of the ; dashboard (which starts character row 18) ECELL = SCBASE+$2400+23*40+11 ; The address in screen RAM of the colour byte ; for the E.C.M. indicator bulb ("E") SCELL = SCBASE+$2400+23*40+28 ; The address in screen RAM of the colour byte ; for the space station indicator bulb ("S") MCELL = SCBASE+$2400+24*40+6 ; The address in screen RAM of the colour byte ; for the first missile indicator TAP% = $CF00 ; The staging area where we copy files after loading and ; before saving XX21 = $D000 ; The address of the ship blueprints lookup table, as ; set in elite-data.asm E% = $D042 ; The address of the default NEWB ship bytes, as set in ; elite-data.asm KWL% = $D063 ; The address of the kill tally fraction table, as set ; in elite-data.asm KWH% = $D084 ; The address of the kill tally integer table, as set in ; elite-data.asm VIC = $D000 ; Registers for the VIC-II video controller chip, which ; are memory-mapped to the 46 bytes from $D000 to $D02E ; (see page 454 of the Programmer's Reference Guide) SID = $D400 ; Registers for the SID sound synthesis chip, which are ; memory-mapped to the 29 bytes from $D400 to $D41C (see ; page 461 of the Programmer's Reference Guide) CIA = $DC00 ; Registers for the CIA1 I/O interface chip, which ; are memory-mapped to the 16 bytes from $DC00 to $DC0F ; (see page 428 of the Programmer's Reference Guide) CIA2 = $DD00 ; Registers for the CIA2 I/O interface chip, which ; are memory-mapped to the 16 bytes from $DD00 to $DD0F ; (see page 428 of the Programmer's Reference Guide) IF _GMA_RELEASE DSTORE% = SCBASE + $AF90 ; The address of a copy of the dashboard bitmap, ; which gets copied into screen memory when ; setting up a new screen SPRITELOC% = SCBASE + $2800 ; The address where the sprite definitions get ; copied to during the loading process (the ; screen bitmap at SCBASE is $2000 bytes long, ; and it's followed by $400 bytes of screen RAM ; for the space view and another $400 for the ; text view, and we put the sprite definitions ; after this) ELIF _SOURCE_DISK DSTORE% = SCBASE + $2800 ; The address of a copy of the dashboard bitmap, ; which gets copied into screen memory when ; setting up a new screen SPRITELOC% = SCBASE + $3100 ; The address where the sprite definitions get ; copied to during the loading process (the ; screen bitmap at SCBASE is $2000 bytes long, ; and it's followed by $400 bytes of screen RAM ; for the space view and another $400 for the ; text view, then $900 bytes for the copy of the ; dashboard bitmap at DSTORE%, and we put the ; sprite definitions after this) ENDIF SPOFF% = (SPRITELOC% - SCBASE) / 64 ; Sprite pointers are defined as the ; offset from the start of the VIC-II ; screen bank to start of the sprite ; definitions, divided by 64, so SPOFF% ; is the offset for the first sprite ; definition at SPRITELOC% LS% = $FFC0 ; The start of the descending ship line heap KERNALSVE = $FFD8 ; The Kernal function to save a file to a device KERNALSETLFS = $FFBA ; The Kernal function to set the logical, first, and ; second addresses for file access KERNALSETNAM = $FFBD ; The Kernal function to set a filename KERNALSETMSG = $FF90 ; The Kernal function to control Kernal messages KERNALLOAD = $FFD5 ; The Kernal function to load a file from a device ; ****************************************************************************** ; ; Name: ZP ; Type: Workspace ; Address: $0000 to $00D1 ; Category: Workspaces ; Summary: Lots of important variables are stored in the zero page workspace ; as it is quicker and more space-efficient to access memory here ; ; ****************************************************************************** ORG $0000 ; Set the assembly address to $0000 .ZP SKIP 2 ; These two bytes control the 6510 processor port ; ; In particular, Elite uses the 6510 port register at ; location $0001 to reconfigure memory on numerous ; occasions ; ; For example, when it needs to access the memory-mapped ; registers in the VIC-II video controller chip, the SID ; sound chip or the two CIA I/O chips, the I/O memory is ; paged in by updating location $0001, and is then paged ; out again afterwards ; ; See the SETL1 routine for more details .RAND SKIP 4 ; Four 8-bit seeds for the random number generation ; system implemented in the DORND routine .T1 SKIP 1 ; Temporary storage, used in a number of places .SC SKIP 1 ; Screen address (low byte) ; ; Elite draws on-screen by poking bytes directly into ; screen memory, and SC(1 0) is typically set to the ; address of the character block containing the pixel ; we want to draw .SCH SKIP 1 ; Screen address (high byte) .XX1 SKIP 0 ; This is an alias for INWK that is used in the main ; ship-drawing routine at LL9 .INWK SKIP 33 ; The zero-page internal workspace for the current ship ; data block ; ; As operations on zero page locations are faster and ; have smaller opcodes than operations on the rest of ; the addressable memory, Elite tends to store oft-used ; data here. A lot of the routines in Elite need to ; access and manipulate ship data, so to make this an ; efficient exercise, the ship data is first copied from ; the ship data blocks at K% into INWK (or, when new ; ships are spawned, from the blueprints at XX21) .XX19 SKIP NI% - 34 ; XX19(1 0) shares its location with INWK(34 33), which ; contains the address of the ship line heap .NEWB SKIP 1 ; The ship's "new byte flags" (or NEWB flags) ; ; Contains details about the ship's type and associated ; behaviour, such as whether they are a trader, a bounty ; hunter, a pirate, currently hostile, in the process of ; docking, inside the hold having been scooped, and so ; on. The default values for each ship type are taken ; from the table at E% .P SKIP 3 ; Temporary storage, used in a number of places .XC SKIP 1 ; The x-coordinate of the text cursor (i.e. the text ; column), which can be from 0 to 32 ; ; A value of 0 denotes the leftmost column and 32 the ; rightmost column, but because the top part of the ; screen (the space view) has a border box that ; clashes with columns 0 and 32, text is only shown ; in columns 1-31 .COL SKIP 1 ; Temporary storage, used to store colour information ; when drawing pixels in the dashboard .YC SKIP 1 ; The y-coordinate of the text cursor (i.e. the text ; row), which can be from 0 to 23 ; ; The screen actually has 31 character rows if you ; include the dashboard, but the text printing routines ; only work on the top part (the space view), so the ; text cursor only goes up to a maximum of 23, the row ; just before the screen splits ; ; A value of 0 denotes the top row, but because the ; top part of the screen has a border box that clashes ; with row 0, text is always shown at row 1 or greater .QQ17 SKIP 1 ; Contains a number of flags that affect how text tokens ; are printed, particularly capitalisation ; ; * If all bits are set (255) then text printing is ; disabled ; ; * Bit 7: 0 = ALL CAPS ; 1 = Sentence Case, bit 6 determines the ; case of the next letter to print ; ; * Bit 6: 0 = print the next letter in upper case ; 1 = print the next letter in lower case ; ; * Bits 0-5: If any of bits 0-5 are set, print in ; lower case ; ; So: ; ; * QQ17 = 0 means case is set to ALL CAPS ; ; * QQ17 = %10000000 means Sentence Case, currently ; printing upper case ; ; * QQ17 = %11000000 means Sentence Case, currently ; printing lower case ; ; * QQ17 = %11111111 means printing is disabled .K3 SKIP 0 ; Temporary storage, used in a number of places .XX2 SKIP 14 ; Temporary storage, used to store the visibility of the ; ship's faces during the ship-drawing routine at LL9 .K4 SKIP 2 ; Temporary storage, used in a number of places .XX16 SKIP 18 ; Temporary storage for a block of values, used in a ; number of places .XX0 SKIP 2 ; Temporary storage, used to store the address of a ship ; blueprint. For example, it is used when we add a new ; ship to the local bubble in routine NWSHP, and it ; contains the address of the current ship's blueprint ; as we loop through all the nearby ships in the main ; flight loop .INF SKIP 2 ; Temporary storage, typically used for storing the ; address of a ship's data block, so it can be copied ; to and from the internal workspace at INWK .V SKIP 2 ; Temporary storage, typically used for storing an ; address pointer .XX SKIP 2 ; Temporary storage, typically used for storing a 16-bit ; x-coordinate .YY SKIP 2 ; Temporary storage, typically used for storing a 16-bit ; y-coordinate .SUNX SKIP 2 ; The 16-bit x-coordinate of the vertical centre axis ; of the sun (which might be off-screen) .BETA SKIP 1 ; The current pitch angle beta, which is reduced from ; JSTY to a sign-magnitude value between -8 and +8 ; ; This describes how fast we are pitching our ship, and ; determines how fast the universe pitches around us ; ; The sign bit is also stored in BET2, while the ; opposite sign is stored in BET2+1 .BET1 SKIP 1 ; The magnitude of the pitch angle beta, i.e. |beta|, ; which is a positive value between 0 and 8 .QQ22 SKIP 2 ; The two hyperspace countdown counters ; ; Before a hyperspace jump, both QQ22 and QQ22+1 are ; set to 15 ; ; QQ22 is an internal counter that counts down by 1 ; each time TT102 is called, which happens every ; iteration of the main game loop. When it reaches ; zero, the on-screen counter in QQ22+1 gets ; decremented, and QQ22 gets set to 5 and the countdown ; continues (so the first tick of the hyperspace counter ; takes 15 iterations to happen, but subsequent ticks ; take 5 iterations each) ; ; QQ22+1 contains the number that's shown on-screen ; during the countdown. It counts down from 15 to 1, and ; when it hits 0, the hyperspace engines kick in .ECMA SKIP 1 ; The E.C.M. countdown timer, which determines whether ; an E.C.M. system is currently operating ; ; * 0 = E.C.M. is off ; ; * Non-zero = E.C.M. is on and is counting down ; ; The counter starts at 32 when an E.C.M. is activated, ; either by us or by an opponent, and it decreases by 1 ; in each iteration of the main flight loop until it ; reaches zero, at which point the E.C.M. switches off. ; Only one E.C.M. can be active at any one time, so ; there is only one counter .ALP1 SKIP 1 ; Magnitude of the roll angle alpha, i.e. |alpha|, ; which is a positive value between 0 and 31 .ALP2 SKIP 2 ; Bit 7 of ALP2 = sign of the roll angle in ALPHA ; ; Bit 7 of ALP2+1 = opposite sign to ALP2 and ALPHA .XX15 SKIP 0 ; Temporary storage, typically used for storing screen ; coordinates in line-drawing routines ; ; There are six bytes of storage, from XX15 TO XX15+5. ; The first four bytes have the following aliases: ; ; X1 = XX15 ; Y1 = XX15+1 ; X2 = XX15+2 ; Y2 = XX15+3 ; ; These are typically used for describing lines in terms ; of screen coordinates, i.e. (X1, Y1) to (X2, Y2) ; ; The last two bytes of XX15 do not have aliases .X1 SKIP 1 ; Temporary storage, typically used for x-coordinates in ; the line-drawing routines .Y1 SKIP 1 ; Temporary storage, typically used for y-coordinates in ; line-drawing routines .X2 SKIP 1 ; Temporary storage, typically used for x-coordinates in ; the line-drawing routines .Y2 SKIP 1 ; Temporary storage, typically used for y-coordinates in ; line-drawing routines SKIP 2 ; The last two bytes of the XX15 block .XX12 SKIP 6 ; Temporary storage for a block of values, used in a ; number of places .K SKIP 4 ; Temporary storage, used in a number of places .LAS SKIP 1 ; Contains the laser power of the laser fitted to the ; current space view (or 0 if there is no laser fitted ; to the current view) ; ; This gets set to bits 0-6 of the laser power byte from ; the commander data block, which contains the laser's ; power (bit 7 doesn't denote laser power, just whether ; or not the laser pulses, so that is not stored here) .MSTG SKIP 1 ; The current missile lock target ; ; * $FF = no target ; .thiskey SKIP 1 ; If a key is being pressed that is not in the keyboard ; table at KYTB, it can be stored in KL and thiskey (as ; seen in routine DK4, for example) .LSP SKIP 1 ; The ball line heap pointer, which contains the number ; of the first free byte after the end of the LSX2 and ; LSY2 heaps .QQ15 SKIP 6 ; The three 16-bit seeds for the selected system, i.e. ; the one in the crosshairs in the Short-range Chart .K5 SKIP 0 ; Temporary storage used to store segment coordinates ; across successive calls to BLINE, the ball line ; routine .XX18 SKIP 4 ; Temporary storage used to store coordinates in the ; LL9 ship-drawing routine .K6 SKIP 5 ; Temporary storage, typically used for storing ; coordinates during vector calculations .QQ19 SKIP 6 ; Temporary storage, used in a number of places .BET2 SKIP 2 ; Bit 7 of BET2 = sign of the pitch angle in BETA ; ; Bit 7 of BET2+1 = opposite sign to BET2 and BETA .DELTA SKIP 1 ; Our current speed, in the range 1-40 .DELT4 SKIP 2 ; Our current speed * 64 as a 16-bit value ; ; This is stored as DELT4(1 0), so the high byte in ; DELT4+1 therefore contains our current speed / 4 .U SKIP 1 ; Temporary storage, used in a number of places .Q SKIP 1 ; Temporary storage, used in a number of places .R SKIP 1 ; Temporary storage, used in a number of places .S SKIP 1 ; Temporary storage, used in a number of places .XSAV SKIP 1 ; Temporary storage for saving the value of the X ; register, used in a number of places .YSAV SKIP 1 ; Temporary storage for saving the value of the Y ; register, used in a number of places .XX17 SKIP 1 ; Temporary storage, used in BPRNT to store the number ; of characters to print, and as the edge counter in the ; main ship-drawing routine .QQ11 SKIP 1 ; The type of the current view: ; ; 0 = Space view ; 1 = Data on System screen (key "7") ; Get commander name ("@", save/load commander) ; In-system jump just arrived ("J") ; 2 = Buy Cargo screen (key "2") ; 3 = Mis-jump just arrived (witchspace) ; 4 = Sell Cargo screen (key "3") ; 6 = Death screen ; 8 = Status Mode screen (key "9") ; Inventory screen (key "0") ; 13 = Title screen ; 16 = Market Price screen (key "8") ; 32 = Equip Ship screen (key "4") ; 64 = Long-range Chart (key "5") ; 128 = Short-range Chart (key "6") ; 255 = Launch view ; ; This value is typically set by calling routine TT66 .ZZ SKIP 1 ; Temporary storage, typically used for distance values .XX13 SKIP 1 ; Temporary storage, typically used in the line-drawing ; routines .MCNT SKIP 1 ; The main loop counter ; ; This counter determines how often certain actions are ; performed within the main loop .DL SKIP 1 ; This byte is unused in this version of Elite (it is ; used to store the vertical sync flag in the BBC Micro ; versions) .TYPE SKIP 1 ; The current ship type ; ; This is where we store the current ship type for when ; we are iterating through the ships in the local bubble ; as part of the main flight loop. See the table at XX21 ; for information about ship types .ALPHA SKIP 1 ; The current roll angle alpha, which is reduced from ; JSTX to a sign-magnitude value between -31 and +31 ; ; This describes how fast we are rolling our ship, and ; determines how fast the universe rolls around us ; ; The sign bit is also stored in ALP2, while the ; opposite sign is stored in ALP2+1 ;.PBUP ; These instructions are commented out in the original ; ; source ;SKIP 1 ;.HBUP ; These instructions are commented out in the original ; ; source ;SKIP 1 ;.LBUP ; These instructions are commented out in the original ; ; source ;SKIP 1 .QQ12 SKIP 1 ; Our "docked" status ; ; * 0 = we are not docked ; ; * $FF = we are docked .TGT SKIP 1 ; Temporary storage, typically used as a target value ; for counters when drawing explosion clouds and partial ; circles .FLAG SKIP 1 ; A flag that's used to define whether this is the first ; call to the ball line routine in BLINE, so it knows ; whether to wait for the second call before storing ; segment data in the ball line heap .CNT SKIP 1 ; Temporary storage, typically used for storing the ; number of iterations required when looping .CNT2 SKIP 1 ; Temporary storage, used in the planet-drawing routine ; to store the segment number where the arc of a partial ; circle should start .STP SKIP 1 ; The step size for drawing circles ; ; Circles in Elite are split up into 64 points, and the ; step size determines how many points to skip with each ; straight-line segment, so the smaller the step size, ; the smoother the circle. The values used are: ; ; * 2 for big planets and the circles on the charts ; ; * 4 for medium planets and the launch tunnel ; ; * 8 for small planets and the hyperspace tunnel ; ; As the step size increases we move from smoother ; circles at the top to more polygonal at the bottom. ; See the CIRCLE2 routine for more details .XX4 SKIP 1 ; Temporary storage, used in a number of places .XX20 SKIP 1 ; Temporary storage, used in a number of places .XX14 SKIP 1 ; This byte appears to be unused .RAT SKIP 1 ; Used to store different signs depending on the current ; space view, for use in calculating stardust movement .RAT2 SKIP 1 ; Temporary storage, used to store the pitch and roll ; signs when moving objects and stardust .K2 SKIP 4 ; Temporary storage, used in a number of places .widget SKIP 1 ; Temporary storage, used to store the original argument ; in A in the logarithmic FMLTU and LL28 routines .dontclip SKIP 1 ; A flag that controls whether the LL145 routine clips ; lines to the dimensions of the space view (which we ; want to disable in the Short-range Chart, as there is ; no dashboard and the chart needs to use the whole ; screen) ; ; * Bit 7 clear = clipping is enabled ; ; * Bit 7 set = clipping is disabled .Yx2M1 SKIP 1 ; This is used to store the number of pixel rows in the ; space view minus 1, which is also the y-coordinate of ; the bottom pixel row of the space view (it is set to ; 191 in the RES2 routine) .messXC SKIP 1 ; Temporary storage, used to store the text column ; of the in-flight message in MESS, so it can be erased ; from the screen at the correct time .newzp SKIP 1 ; This is used by the STARS2 routine for storing the ; stardust particle's delta_x value .T SKIP 1 ; Temporary storage, used in a number of places .P2 SKIP 1 ; Temporary storage, used in place of variable P in the ; line-drawing routines .Q2 SKIP 1 ; Temporary storage, used in place of variable Q in the ; line-drawing routines .R2 SKIP 1 ; Temporary storage, used in place of variable R in the ; line-drawing routines .S2 SKIP 1 ; Temporary storage, used in place of variable S in the ; line-drawing routines .T2 SKIP 1 ; Temporary storage, used in place of variable T in the ; line-drawing routines SKIP 1 ; This byte appears to be unused .BDdataptr1 SKIP 1 ; The low byte of the address of the music data pointer ; in BDdataptr1(1 0), which points to the end of the ; music data currently being processed .BDdataptr2 SKIP 1 ; The high byte of the address of the music data pointer ; in BDdataptr1(1 0), which points to the end of the ; music data currently being processed .BDdataptr3 SKIP 1 ; The low byte of the address of the music data pointer ; in BDdataptr3(1 0), which is a backup of the initial ; value of the BDdataptr1(1 0) pointer, so music can be ; repeated .BDdataptr4 SKIP 1 ; The high byte of the address of the music data pointer ; in BDdataptr3(1 0), which is a backup of the initial ; value of the BDdataptr1(1 0) pointer, so music can be ; repeated .counter SKIP 1 ; The rest counter when playing music, for implementing ; music commands #8 and #15 .vibrato2 SKIP 1 ; The vibrato counter for voice 2 .vibrato3 SKIP 1 ; The vibrato counter for voice 3 .voice2lo1 SKIP 1 ; The high byte of the first vibrato frequency for ; voice 2, which contains the lower frequency ; ; Note that the vibrato labels have hi and lo the wrong ; way around .voice2hi1 SKIP 1 ; The low byte of the first vibrato frequency for ; voice 2, which contains the lower frequency ; ; Note that the vibrato labels have hi and lo the wrong ; way around .voice2lo2 SKIP 1 ; The high byte of the second vibrato frequency for ; voice 2, which contains the higher frequency ; ; Note that the vibrato labels have hi and lo the wrong ; way around .voice2hi2 SKIP 1 ; The low byte of the second vibrato frequency for ; voice 2, which contains the higher frequency ; ; Note that the vibrato labels have hi and lo the wrong ; way around .voice3lo1 SKIP 1 ; The high byte of the first vibrato frequency for ; voice 3, which contains the lower frequency ; ; Note that the vibrato labels have hi and lo the wrong ; way around .voice3hi1 SKIP 1 ; The low byte of the first vibrato frequency for ; voice 3, which contains the lower frequency ; ; Note that the vibrato labels have hi and lo the wrong ; way around .voice3lo2 SKIP 1 ; The high byte of the second vibrato frequency for ; voice 3, which contains the higher frequency ; ; Note that the vibrato labels have hi and lo the wrong ; way around .voice3hi2 SKIP 1 ; The low byte of the second vibrato frequency for ; voice 3, which contains the higher frequency ; ; Note that the vibrato labels have hi and lo the wrong ; way around .BDBUFF SKIP 1 ; The music data byte that is currently being processed, ; with the low nibble being processed first, and then ; the high nibble PRINT "ZP workspace from ", ~ZP, "to ", ~P%-1, "inclusive" ; ****************************************************************************** ; ; Name: XX3 ; Type: Workspace ; Address: $0100 to the top of the descending stack ; Category: Workspaces ; Summary: Temporary storage space for complex calculations ; ; ------------------------------------------------------------------------------ ; ; Used as heap space for storing temporary data during calculations. Shared with ; the descending 6502 stack, which works down from $01FF. ; ; ****************************************************************************** ORG $0100 ; Set the assembly address to $0100 .XX3 SKIP 256 ; Temporary storage, typically used for storing tables ; of values such as screen coordinates or ship data ; ****************************************************************************** ; ; Name: K% ; Type: Workspace ; Address: $F900 to $FA71 ($F000 to $F171 in the source disk variant) ; Category: Workspaces ; Summary: Ship data blocks and ship line heaps ; Deep dive: Ship data blocks ; The local bubble of universe ; ; ------------------------------------------------------------------------------ ; ; Contains ship data for all the ships, planets, suns and space stations in our ; local bubble of universe. ; ; The blocks are pointed to by the lookup table at location UNIV. The first 370 ; bytes of the K% workspace hold ship data on up to 10 ships, with 37 (NI%) ; bytes per ship. ; ; ****************************************************************************** IF _GMA_RELEASE ORG $F900 ; Set the assembly address to $F900 ELIF _SOURCE_DISK ORG $F000 ; Set the assembly address to $F000 ENDIF .K% SKIP NOSH * NI% ; Ship data blocks and ship line heap PRINT "K% workspace from ", ~K%, "to ", ~P%-1, "inclusive" ; ****************************************************************************** ; ; Name: UP ; Type: Workspace ; Address: $0400 to $0540 ; Category: Workspaces ; Summary: Configuration variables ; ; ****************************************************************************** ORG $0400 ; Set the assembly address to $0400 .UP SKIP 0 ; The start of the UP workspace ;.QQ16 ; This QQ16 label is present in the original source, but ; it is overridden by the QQ16 label in the ELITE A ; section, so this declaration has no effect. BeebAsm ; does not allow labels to be defined twice, so this one ; is commented out SKIP 65 ; These bytes appear to be unused .KL SKIP 1 ; If a key is being pressed that is not in the keyboard ; table at KYTB, it can be stored here (as seen in ; routine DK4, for example) SKIP 16 ; These bytes appear to be unused .FRIN SKIP NOSH + 1 ; Slots for the ships in the local bubble of universe ; ; There are #NOSH + 1 slots, but the ship-spawning ; routine at NWSHP only populates #NOSH of them, so ; (the last slot is effectively used as a null ; terminator when shuffling the slots down in the ; KILLSHP routine) .MANY SKIP SST ; The number of ships of each type in the local bubble ; of universe ; ; The number of ships of type X in the local bubble is ; stored at MANY+X .SSPR SKIP NTY + 1 - SST ; "Space station present" flag ; ; * Non-zero if we are inside the space station's safe ; zone ; ; ; This flag is at MANY+SST, which is no coincidence, as ; MANY+SST is a count of how many space stations there ; are in our local bubble, which is the same as saying ; "space station present" .JUNK SKIP 1 ; The amount of junk in the local bubble ; ; "Junk" is defined as being one of these: ; ; * Escape pod ; * Alloy plate ; * Cargo canister ; * Asteroid ; * Splinter ; * Shuttle ; * Transporter .auto SKIP 1 ; Docking computer activation status ; ; * 0 = Docking computer is off ; ; * Non-zero = Docking computer is running .ECMP SKIP 1 ; Our E.C.M. status ; ; * 0 = E.C.M. is off ; ; * Non-zero = E.C.M. is on .MJ SKIP 1 ; Are we in witchspace (i.e. have we mis-jumped)? ; ; * 0 = no, we are in normal space ; ; * $FF = yes, we are in witchspace .CABTMP SKIP 1 ; Cabin temperature ; ; The ambient cabin temperature in deep space is 30, ; which is displayed as one notch on the dashboard bar ; ; We get higher temperatures closer to the sun ; ; CABTMP shares a location with MANY, but that's OK as ; MANY+0 would contain the number of ships of type 0, ; and as there is no ship type 0 (they start at 1), the ; byte at MANY+0 is not used for storing a ship type ; and can be used for the cabin temperature instead .LAS2 SKIP 1 ; Laser power for the current laser ; ; * Bits 0-6 contain the laser power of the current ; space view ; ; * Bit 7 denotes whether or not the laser pulses: ; ; * 0 = pulsing laser ; ; * 1 = beam laser (i.e. always on) .MSAR SKIP 1 ; The targeting state of our leftmost missile ; ; * 0 = missile is not looking for a target, or it ; already has a target lock (indicator is not ; yellow/white) ; ; * Non-zero = missile is currently looking for a ; target (indicator is yellow/white) .VIEW SKIP 1 ; The number of the current space view ; ; * 0 = front ; * 1 = rear ; * 2 = left ; * 3 = right .LASCT SKIP 1 ; The laser pulse count for the current laser ; ; This is a counter that defines the gap between the ; pulses of a pulse laser. It is set as follows: ; ; * 0 for a beam laser ; ; * 10 for a pulse laser ; ; It gets decremented by 2 on each iteration around the ; main game loop and is set to a non-zero value for ; pulse lasers only ; ; The laser only fires when the value of LASCT hits ; zero, so for pulse lasers with a value of 10, that ; means the laser fires once every five iterations ; around the main game loop (LASCT = 10, 8, 6, 4, 2, 0) ; ; In comparison, beam lasers fire continuously as the ; value of LASCT is always 0 .GNTMP SKIP 1 ; Laser temperature (or "gun temperature") ; ; If the laser temperature exceeds 242 then the laser ; overheats and cannot be fired again until it has ; cooled down .HFX SKIP 1 ; This flag is unused in this version of Elite. In the ; other versions, setting HFX to a non-zero value makes ; the hyperspace rings multi-coloured, but that effect ; is not used in this version .EV SKIP 1 ; The "extra vessels" spawning counter ; ; This counter is set to 0 on arrival in a system and ; following an in-system jump, and is bumped up when we ; spawn bounty hunters or pirates (i.e. "extra vessels") ; ; It decreases by 1 each time we consider spawning more ; "extra vessels" in part 4 of the main game loop, so ; increasing the value of EV has the effect of delaying ; the spawning of more vessels ; ; In other words, this counter stops bounty hunters and ; pirates from continually appearing, and ensures that ; there's a delay between spawnings .DLY SKIP 1 ; In-flight message delay ; ; This counter is used to keep an in-flight message up ; for a specified time before it gets removed. The value ; in DLY is decremented each time we start another ; iteration of the main game loop at TT100 .de SKIP 1 ; Equipment destruction flag ; ; * Bit 1 denotes whether or not the in-flight message ; about to be shown by the MESS routine is about ; destroyed equipment: ; ; * 0 = the message is shown normally ; ; * 1 = the string " DESTROYED" gets added to the ; end of the message .JSTX SKIP 1 ; Our current roll rate ; ; This value is shown in the dashboard's RL indicator, ; and determines the rate at which we are rolling ; ; The value ranges from 1 to 255 with 128 as the centre ; point, so 1 means roll is decreasing at the maximum ; rate, 128 means roll is not changing, and 255 means ; roll is increasing at the maximum rate ; ; This value is updated by "<" and ">" key presses, or ; if joysticks are enabled, from the joystick. If ; keyboard damping is enabled (which it is by default), ; the value is slowly moved towards the centre value of ; 128 (no roll) if there are no key presses or joystick ; movement .JSTY SKIP 1 ; Our current pitch rate ; ; This value is shown in the dashboard's DC indicator, ; and determines the rate at which we are pitching ; ; The value ranges from 1 to 255 with 128 as the centre ; point, so 1 means pitch is decreasing at the maximum ; rate, 128 means pitch is not changing, and 255 means ; pitch is increasing at the maximum rate ; ; This value is updated by "S" and "X" key presses, or ; if joysticks are enabled, from the joystick. If ; keyboard damping is enabled (which it is by default), ; the value is slowly moved towards the centre value of ; 128 (no pitch) if there are no key presses or joystick ; movement .XSAV2 SKIP 1 ; Temporary storage, used for storing the value of the X ; register in the CHPR routine .YSAV2 SKIP 1 ; Temporary storage, used for storing the value of the Y ; register in the CHPR routine .NAME SKIP 8 ; The current commander name ; ; The commander name can be up to 7 characters (the DFS ; limit for filenames), and is terminated by a carriage ; return .TP SKIP 1 ; The current mission status ; ; * Bits 0-1 = Mission 1 status ; ; * %00 = Mission not started ; * %01 = Mission in progress, hunting for ship ; * %11 = Constrictor killed, not debriefed yet ; * %10 = Mission and debrief complete ; ; * Bits 2-3 = Mission 2 status ; ; * %00 = Mission not started ; * %01 = Mission in progress, plans not picked up ; * %10 = Mission in progress, plans picked up ; * %11 = Mission complete ; ; * Bit 4 = Trumble mission status ; ; * %0 = Trumbles not yet offered ; * %1 = Trumbles accepted or declined .QQ0 SKIP 1 ; The current system's galactic x-coordinate (0-256) .QQ1 SKIP 1 ; The current system's galactic y-coordinate (0-256) .QQ21 SKIP 6 ; The three 16-bit seeds for the current galaxy ; ; These seeds define system 0 in the current galaxy, so ; they can be used as a starting point to generate all ; 256 systems in the galaxy ; ; Using a galactic hyperdrive rotates each byte to the ; left (rolling each byte within itself) to get the ; seeds for the next galaxy, so after eight galactic ; jumps, the seeds roll around to the first galaxy again .CASH SKIP 4 ; Our current cash pot ; ; The cash stash is stored as a 32-bit unsigned integer, ; with the most significant byte in CASH and the least ; significant in CASH+3. This is big-endian, which is ; the opposite way round to most of the numbers used in ; Elite - to use our notation for multi-byte numbers, ; the amount of cash is CASH(0 1 2 3) .QQ14 SKIP 1 ; Our current fuel level (0-70) ; ; The fuel level is stored as the number of light years ; multiplied by 10, so QQ14 = 1 represents 0.1 light ; years, and the maximum possible value is 70, for 7.0 ; light years .COK SKIP 1 ; Flags used to generate the competition code .GCNT SKIP 1 ; The number of the current galaxy (0-7) ; ; When this is displayed in-game, 1 is added to the ; number, so we start in galaxy 1 in-game, but it's ; stored as galaxy 0 internally ; ; The galaxy number increases by one every time a ; galactic hyperdrive is used, and wraps back around to ; the start after eight galaxies .LASER SKIP 4 ; The specifications of the lasers fitted to each of the ; four space views ; ; * Byte #0 = front view ; ; * Byte #1 = rear view ; ; * Byte #2 = left view ; ; * Byte #3 = right view ; ; The value for each view is as follows: ; ; * 0 = no laser is fitted to this view ; ; * Non-zero = a laser is fitted to this view, with ; the following specification: ; ; * Bits 0-6 contain the laser's power ; ; * Bit 7 determines whether or not the laser pulses ; (0 = pulse or mining laser) or is always on ; (1 = beam or military laser) SKIP 2 ; These bytes appear to be unused (they were originally ; used for up/down lasers, but they were dropped) .CRGO SKIP 1 ; Our ship's cargo capacity ; ; * 22 = standard cargo bay of 20 tonnes ; ; * 37 = large cargo bay of 35 tonnes ; ; The value is two greater than the actual capacity to ; make the maths in tnpr slightly more efficient .QQ20 SKIP 17 ; The contents of our cargo hold ; ; The amount of market item X that we have in our hold ; can be found in the X-th byte of QQ20. For example: ; ; * QQ20 contains the amount of food (item 0) ; ; * QQ20+7 contains the amount of computers (item 7) ; ; See QQ23 for a list of market item numbers and their ; storage units .ECM SKIP 1 ; E.C.M. system ; ; * 0 = not fitted ; ; * $FF = fitted .BST SKIP 1 ; Fuel scoops (BST stands for "barrel status") ; ; * 0 = not fitted ; ; * $FF = fitted .BOMB SKIP 1 ; Energy bomb ; ; * 0 = not fitted ; ; * $7F = fitted .ENGY SKIP 1 ; Energy unit ; ; * 0 = not fitted ; ; * Non-zero = fitted ; ; The actual value determines the refresh rate of our ; energy banks, as they refresh by ENGY+1 each time (so ; our ship's energy level goes up by 2 each time if we ; have an energy unit fitted, otherwise it goes up by 1) .DKCMP SKIP 1 ; Docking computer ; ; * 0 = not fitted ; ; * $FF = fitted .GHYP SKIP 1 ; Galactic hyperdrive ; ; * 0 = not fitted ; ; * $FF = fitted .ESCP SKIP 1 ; Escape pod ; ; * 0 = not fitted ; ; * $FF = fitted SKIP 1 ; This byte appears to be unused .TRIBBLE SKIP 2 ; The number of Trumbles in the cargo hold .TALLYL SKIP 1 ; Combat rank fraction ; ; Contains the fraction part of the kill count, which ; together with the integer in TALLY(1 0) determines our ; combat rank. The fraction is stored as the numerator ; of a fraction with a denominator of 256, so a TALLYL ; of 128 would represent 0.5 (i.e. 128 / 256) .NOMSL SKIP 1 ; The number of missiles we have fitted (0-4) .FIST SKIP 1 ; Our legal status (FIST stands for "fugitive/innocent ; status") ; ; * 0 = Clean ; ; * 1-49 = Offender ; ; * 50+ = Fugitive ; ; You get 64 points if you kill a cop, so that's a fast ; ticket to fugitive status .AVL SKIP 17 ; Market availability in the current system ; ; The available amount of market item X is stored in ; the X-th byte of AVL, so for example: ; ; * AVL contains the amount of food (item 0) ; ; * AVL+7 contains the amount of computers (item 7) ; ; See QQ23 for a list of market item numbers and their ; storage units .QQ26 SKIP 1 ; A random value used to randomise market data ; ; This value is set to a new random number for each ; change of system, so we can add a random factor into ; the calculations for market prices .TALLY SKIP 2 ; Our combat rank ; ; The combat rank is stored as the number of kills, in a ; 16-bit number TALLY(1 0) - so the high byte is in ; TALLY+1 and the low byte in TALLY ; ; There is also a fractional part of the kill count, ; which is stored in TALLYL ; ; If the high byte in TALLY+1 is 0 then we have between ; 0 and 255 kills, so our rank is Harmless, Mostly ; Harmless, Poor, Average Above Average or Competent, ; according to the value of the low byte in TALLY: ; ; Harmless %00000000 to %00000111 = 0 to 7 ; Mostly Harmless %00001000 to %00001111 = 8 to 15 ; Poor %00010000 to %00011111 = 16 to 31 ; Average %00100000 to %00111111 = 32 to 63 ; Above Average %01000000 to %01111111 = 64 to 127 ; Competent %10000000 to %11111111 = 128 to 255 ; ; Note that the Competent range also covers kill counts ; from 256 to 511, as follows ; ; If the high byte in TALLY+1 is non-zero then we are ; Competent, Dangerous, Deadly or Elite, according to ; the value of TALLY(1 0): ; ; Competent (1 0) to (1 255) = 256 to 511 kills ; Dangerous (2 0) to (9 255) = 512 to 2559 kills ; Deadly (10 0) to (24 255) = 2560 to 6399 kills ; Elite (25 0) and up = 6400 kills and up ; ; You can see the rating calculation in the STATUS ; subroutine .SVC SKIP 1 ; The save count ; ; When a new commander is created, the save count gets ; set to 128. This value gets halved each time the ; commander file is saved, but it is otherwise unused. ; It is presumably part of the security system for the ; competition, possibly another flag to catch out ; entries with manually altered commander files SKIP 2 ; The commander file checksum ; ; These two bytes are reserved for the commander file ; checksum, so when the current commander block is ; copied from here to the last saved commander block at ; NA%, CHK and CHK2 get overwritten SKIP 1 ; The second commander file checksum ; ; This byte is reserved for the second commander file ; checksum in CHK3 NT% = SVC + 3 - TP ; This sets the variable NT% to the size of the current ; commander data block, which starts at TP and ends at ; SVC+3 (inclusive), i.e. with the last checksum byte .MCH SKIP 1 ; The text token number of the in-flight message that is ; currently being shown, and which will be removed by ; the me2 routine when the counter in DLY reaches zero .FSH SKIP 1 ; Forward shield status ; ; * 0 = empty ; ; * $FF = full .ASH SKIP 1 ; Aft shield status ; ; * 0 = empty ; ; * $FF = full .ENERGY SKIP 1 ; Energy bank status ; ; * 0 = empty ; ; * $FF = full .COMX SKIP 1 ; The x-coordinate of the compass dot .COMY SKIP 1 ; The y-coordinate of the compass dot .QQ24 SKIP 1 ; Temporary storage, used to store the current market ; item's price in routine TT151 .QQ25 SKIP 1 ; Temporary storage, used to store the current market ; item's availability in routine TT151 .QQ28 SKIP 1 ; The current system's economy (0-7) ; ; * 0 = Rich Industrial ; * 1 = Average Industrial ; * 2 = Poor Industrial ; * 3 = Mainly Industrial ; * 4 = Mainly Agricultural ; * 5 = Rich Agricultural ; * 6 = Average Agricultural ; * 7 = Poor Agricultural .QQ29 SKIP 1 ; Temporary storage, used in a number of places .gov SKIP 1 ; The current system's government type (0-7) .tek SKIP 1 ; The current system's tech level (0-14) .SLSP SKIP 2 ; The address of the bottom of the ship line heap ; ; The ship line heap is a descending block of memory ; extended downwards by the NWSHP routine when adding ; new ships (and their associated ship line heaps), in ; which case SLSP is lowered to provide more heap space, ; assuming there is enough free memory to do so .QQ2 SKIP 6 ; The three 16-bit seeds for the current system, i.e. ; the one we are currently in .safehouse SKIP 6 ; Backup storage for the seeds for the selected system ; ; The seeds for the current system get stored here as ; soon as a hyperspace is initiated, so we can fetch ; them in the hyp1 routine. This fixes a bug in an ; earlier version where you could hyperspace while ; docking and magically appear in your destination ; station .QQ3 SKIP 1 ; The selected system's economy (0-7) ; ; * 0 = Rich Industrial ; * 1 = Average Industrial ; * 2 = Poor Industrial ; * 3 = Mainly Industrial ; * 4 = Mainly Agricultural ; * 5 = Rich Agricultural ; * 6 = Average Agricultural ; * 7 = Poor Agricultural .QQ4 SKIP 1 ; The selected system's government (0-7) .QQ5 SKIP 1 ; The selected system's tech level (0-14) .QQ6 SKIP 2 ; The selected system's population in billions * 10 ; (1-71), so the maximum population is 7.1 billion .QQ7 SKIP 2 ; The selected system's productivity in M CR (96-62480) .QQ8 SKIP 2 ; The distance from the current system to the selected ; system in light years * 10, stored as a 16-bit number ; ; The distance will be 0 if the selected system is the ; current system ; ; The galaxy chart is 102.4 light years wide and 51.2 ; light years tall (see the intra-system distance ; calculations in routine TT111 for details), which ; equates to 1024 x 512 in terms of QQ8 .QQ9 SKIP 1 ; The galactic x-coordinate of the crosshairs in the ; galaxy chart (and, most of the time, the selected ; system's galactic x-coordinate) .QQ10 SKIP 1 ; The galactic y-coordinate of the crosshairs in the ; galaxy chart (and, most of the time, the selected ; system's galactic y-coordinate) .NOSTM SKIP 1 ; The number of stardust particles shown on screen, ; which is 12 (#NOST) for normal space, and 3 for ; witchspace .COL2 SKIP 1 ; The text colour of the next character to draw in CHPR .frump SKIP 1 ; Used to store the number of particles in the explosion ; cloud .sprx SKIP 1 ; Contains the x-coordinate offset for the explosion ; sprite, i.e. the relative position of the sprite ; compared to the centre of the explosion, which needs ; to be set according to the size of the sprite .spry SKIP 1 ; Contains the y-coordinate offset for the explosion ; sprite, i.e. the relative position of the sprite ; compared to the centre of the explosion, which needs ; to be set according to the size of the sprite .TRIBCT SKIP 1 ; Contains the number of Trumble sprites that we are ; showing on-screen, in the range 0 to 6 .TRIBVX SKIP 16 ; Contains the low byte of the 16-bit x-axis velocity ; of each of the Trumbles ; ; Also contains the 8-bit y-axis velocity ; ; The 16-bit x-axis velocity for Trumble Y is stored in ; (TRIBVXH+Y*2 TRIBVX+Y*2), for Y = 0 to 5 ; ; The 8-bit y-axis velocity for Trumble Y is stored in ; TRIBVX + Y*2 + 1, for Y = 0 to 5 .TRIBVXH SKIP 16 ; Contains the high byte of the 16-bit x-axis velocity ; of each of the Trumbles ; ; The 16-bit x-axis velocity for Trumble Y is stored in ; (TRIBVXH+Y*2 TRIBVX+Y*2), for Y = 0 to 5 .TRIBXH SKIP 16 ; Contains bit 8 of the x-coordinate for each of the ; Trumble sprites (as x-coordinates are 9-bit values) ; ; The top bit for Trumble Y's x-coordinate is stored in ; TRIBXH + Y*2, for Y = 0 to 5 PRINT "UP workspace from ", ~UP, "to ", ~P%-1, "inclusive" ; ****************************************************************************** ; ; Name: WP ; Type: Workspace ; Address: $0580 to $06FB ; Category: Workspaces ; Summary: Variables ; ; ****************************************************************************** ORG $0580 ; Set the assembly address to $0580 .WP SKIP 0 ; The start of the WP workspace .LSX SKIP 0 ; LSX is an alias that points to the first byte of the ; sun line heap at LSO ; ; * $FF indicates the sun line heap is empty ; ; * Otherwise the LSO heap contains the line data for ; the sun .LSO SKIP 200 ; The ship line heap for the space station (see NWSPS) ; and the sun line heap (see SUN) ; ; The spaces can be shared as our local bubble of ; universe can support either the sun or a space ; station, but not both .BUF SKIP 90 ; The line buffer used by DASC to print justified text .SX SKIP NOST + 1 ; This is where we store the x_hi coordinates for all ; the stardust particles .SXL SKIP NOST + 1 ; This is where we store the x_lo coordinates for all ; the stardust particles .SY SKIP NOST + 1 ; This is where we store the y_hi coordinates for all ; the stardust particles .SYL SKIP NOST + 1 ; This is where we store the y_lo coordinates for all ; the stardust particles .SZ SKIP NOST + 1 ; This is where we store the z_hi coordinates for all ; the stardust particles .SZL SKIP NOST + 1 ; This is where we store the z_lo coordinates for all ; the stardust particles .LASX SKIP 1 ; The x-coordinate of the tip of the laser line .LASY SKIP 1 ; The y-coordinate of the tip of the laser line .XX24 SKIP 1 ; This byte appears to be unused .ALTIT SKIP 1 ; Our altitude above the surface of the planet or sun ; ; * 255 = we are a long way above the surface ; ; * 1-254 = our altitude as the square root of: ; ; x_hi^2 + y_hi^2 + z_hi^2 - 6^2 ; ; where our ship is at the origin, the centre of the ; planet/sun is at (x_hi, y_hi, z_hi), and the ; radius of the planet/sun is 6 ; ; * 0 = we have crashed into the surface .SWAP SKIP 1 ; Temporary storage, used to store a flag that records ; whether or not we had to swap a line's start and end ; coordinates around when clipping the line in routine ; LL145 (the flag is used in places like BLINE to swap ; them back) .XP SKIP 1 ; This byte appears to be unused .YP SKIP 1 ; This byte appears to be unused .YS SKIP 1 ; This byte appears to be unused .BALI SKIP 1 ; This byte appears to be unused .UPO SKIP 1 ; This byte appears to be unused .boxsize SKIP 1 ; This byte appears to be unused .distaway SKIP 1 ; Used to store the nearest distance of the rotating ; ship on the title screen PRINT "WP workspace from ", ~WP, "to ", ~P%-1, "inclusive" ; ****************************************************************************** ; ; ELITE A FILE ; ; Produces the binary file ELTA.bin that gets loaded by elite-checksum.py. ; ; ****************************************************************************** ORG CODE% ; Set the assembly address to CODE% LOAD_A% = LOAD% ; ****************************************************************************** ; ; Name: Option variables ; Type: Workspace ; Address: $1D00 to $1D13 ; Category: Workspaces ; Summary: Variables that are predominantly used to store the game options ; ; ****************************************************************************** .MOS SKIP 1 ; This variable appears to be unused .COMC SKIP 1 ; The colour of the dot on the compass ; ; * #YELLOW = the object in the compass is in front of ; us, so the dot is yellow ; ; * #GREEN = the object in the compass is behind us, ; so the dot is green .MUTOKOLD SKIP 1 ; Used to store the previous value of MUTOK, so we can ; track whether the docking music configuration changes .MUPLA SKIP 1 ; A flag to record whether any music is currently ; playing ; ; * 0 = no music is currently playing ; ; * $FF = music is currently playing .DFLAG SKIP 1 ; A flag that indicates whether the dashboard is ; currently being shown on-screen ; ; * 0 = there is no dashboard on-screen ; ; * $FF = the dashboard is on-screen .DNOIZ SKIP 1 ; Sound on/off configuration setting ; ; * 0 = sound is on (default) ; ; * Non-zero = sound is off ; ; Toggled by pressing "S" when paused, see the DK4 ; routine for details .DAMP SKIP 1 ; Keyboard damping configuration setting ; ; * 0 = damping is enabled (default) ; ; * $FF = damping is disabled ; ; Toggled by pressing RUN/STOP when paused, see the ; DKS3 routine for details .DJD SKIP 1 ; Keyboard auto-recentre configuration setting ; ; * 0 = auto-recentre is enabled (default) ; ; * $FF = auto-recentre is disabled ; ; Toggled by pressing "A" when paused, see the DKS3 ; routine for details .PATG SKIP 1 ; Configuration setting to show the author names on the ; start-up screen and enable manual hyperspace mis-jumps ; ; * 0 = no author names or manual mis-jumps (default) ; ; * $FF = show author names and allow manual mis-jumps ; ; Toggled by pressing "X" when paused, see the DKS3 ; routine for details ; ; This needs to be turned on for manual mis-jumps to be ; possible. To do a manual mis-jump, first toggle the ; author display by pausing the game and pressing "X", ; and during the next hyperspace, hold down CTRL to ; force a mis-jump. See routine ee5 for the "AND PATG" ; instruction that implements this logic .FLH SKIP 1 ; Flashing console bars configuration setting ; ; * 0 = static bars (default) ; ; * $FF = flashing bars ; ; Toggled by pressing "F" when paused, see the DKS3 ; routine for details .JSTGY SKIP 1 ; Reverse joystick Y-channel configuration setting ; ; * 0 = standard Y-channel (default) ; ; * $FF = reversed Y-channel ; ; Toggled by pressing "Y" when paused, see the DKS3 ; routine for details .JSTE SKIP 1 ; Reverse both joystick channels configuration setting ; ; * 0 = standard channels (default) ; ; * $FF = reversed channels ; ; Toggled by pressing "J" when paused, see the DKS3 ; routine for details .JSTK SKIP 1 ; Keyboard or joystick configuration setting ; ; * 0 = keyboard (default) ; ; * $FF = joystick ; ; Toggled by pressing "K" when paused, see the DKS3 ; routine for details .MUTOK SKIP 1 ; Docking music configuration setting ; ; * 0 = docking music is enabled (default) ; ; * $FF = docking music is disabled ; ; Toggled by pressing "M" when paused, see the DKS3 ; routine for details .DISK SKIP 1 ; Current media configuration setting ; ; * 0 = tape (default) ; ; * $FF = disk ; ; Toggled by pressing "D" when paused, see the DK4 ; routine for details .PLTOG SKIP 1 ; Planetary details configuration setting ; ; * 0 = planets are drawn as plain circles (default) ; ; * $FF = planets are drawn with craters and meridians ; ; Toggled by pressing "P" when paused, see the DKS3 ; routine for details .MUFOR SKIP 1 ; Configuration setting that controls whether the ; docking music can be enabled or disabled ; ; * 0 = docking music can be toggled (default) ; ; * $FF = docking music cannot be toggled ; ; Toggled by pressing "C" when paused, but only if the ; "X" has also been configured, see the DK4 and DKS3 ; routines for details IF _GMA_RELEASE .MUDOCK SKIP 1 ; Docking music tune configuration setting ; ; * 0 = default tune for docking music ; ; * $FF = play the title music when the docking ; computer is activated ; ; Toggled by pressing "E" when paused, but only if the ; "X" has also been configured, see the DK4 and DKS3 ; routines for details ENDIF .MUSILLY SKIP 1 ; Sounds during music configuration setting ; ; * 0 = sounds are not played during music (default) ; ; * $FF = sounds are played during music ; ; Toggled by pressing "B" when paused, but only if the ; "X" has also been configured, see the DK4 and DKS3 ; routines for details IF _GMA_RELEASE .MULIE SKIP 1 ; A flag to record whether the RESET routine is being ; being called from within the TITLE routine, when the ; title screen is being displayed, as we don't want to ; stop the title music from playing when this is the ; case ; ; * 0 = the RESET routine is not currently being run ; from the call in the TITLE routine, so the ; RESET, RES2 and stopbd routines should stop ; any music that is playing ; ; * $FF = the RESET routine is currently being run ; from the call in the TITLE routine, so ; prevent it from stopping the title music ENDIF ; ****************************************************************************** ; ; Name: TGINT ; Type: Variable ; Category: Keyboard ; Summary: The keys used to toggle configuration settings when the game is ; paused ; ; ****************************************************************************** .TGINT ; The configuration keys in the same order as their ; configuration bytes (starting from DAMP), using their ; internal key numbers as returned by the RDKEY routine EQUB $01 ; Keyboard damping (RUN/STOP) EQUB $36 ; Keyboard auto-recentre ("A") EQUB $29 ; Author names and manual mis-jump ("X") EQUB $2B ; Flashing console bars ("F") EQUB $27 ; Reverse joystick Y-channel ("Y") EQUB $1E ; Reverse both joystick channels ("J") EQUB $1B ; Keyboard or joystick ("K") EQUB $1C ; Docking music toggle ("M") EQUB $2E ; Current media ("D") EQUB $17 ; Planetary details ("P") EQUB $2C ; Allow docking music to be toggled ("C") IF _GMA_RELEASE EQUB $32 ; Docking music tune ("E") ENDIF EQUB $24 ; Allow sounds during music ("B") ; ****************************************************************************** ; ; Name: S% ; Type: Subroutine ; Category: Loader ; Summary: Checksum, decrypt and unscramble the main game code, and start the ; game ; ; ****************************************************************************** RTS ; The checksum byte goes here, at S%-1. In the original ; source this byte is set by the first call to ZP in the ; Big Code File, though in the BeebAsm version this is ; populated by elite-checksum.py .S% CLD ; Clear the D flag to make sure we are in binary mode LDX #2 ; We now copy the contents of zero page between $0002 ; and $00FF to the page at $CE00, so set an index in X ; to start from byte $0002 ; ; We do this so we can use the SWAPPZERO routine to swap ; zero page with the page at $CE00 when saving or ; loading commander files .ZONKPZERO LDA $0000,X ; Copy the X-th byte of zero page to the X-th byte of STA $CE00,X ; $CE00 INX ; Increment the loop counter BNE ZONKPZERO ; Loop back until we have copied all of zero page JSR DEEOR ; Decrypt the main game code between $1300 and $9FFF JSR COLD ; Configure memory, set up interrupt handlers and ; configure the VIC-II, SID and CIA chips ;JSR Checksum ; This instruction is commented out in the original ; source JMP BEGIN ; Jump to BEGIN to start the game ; ****************************************************************************** ; ; Name: DEEOR ; Type: Subroutine ; Category: Utility routines ; Summary: Unscramble the main code ; ; ------------------------------------------------------------------------------ ; ; The main game code and data are encrypted. This routine decrypts the game code ; in two parts: between G% and R%, and between C% and F%. ; ; In the BeebAsm version, the encryption is done by elite-checksum.py, but in ; the original this would have been done by the BBC BASIC build scripts. ; ; ****************************************************************************** .DEEOR LDA #LO(G%-1) ; Set FRIN(1 0) = G%-1 as the low address of the STA FRIN ; decryption block, so we decrypt from the start of the LDA #HI(G%-1) ; DOENTRY routine STA FRIN+1 LDA #HI(R%-1) ; Set (A Y) to R% as the high address of the decryption LDY #LO(R%-1) ; block, so we decrypt to the end of the first block of ; game code at R% (so we decrypt from DOENTRY to the end ; of the ELITE C section) LDX #KEY1 ; Set X = KEY1 as the decryption seed (the value used to ; encrypt the code, which is done in elite-checksum.py) IF _REMOVE_CHECKSUMS NOP ; If we have disabled checksums, skip the call to DEEORS NOP ; and return from the subroutine to skip the second call RTS ; below ELSE JSR DEEORS ; Call DEEORS to decrypt between DOENTRY and F% ENDIF LDA #LO(C%-1) ; Set FRIN(1 0) = C%-1 and as the low address of the STA FRIN ; decryption block, so we decrypt from the start of the LDA #HI(C%-1) ; ELITE D section STA FRIN+1 LDA #HI(F%-1) ; Set (A Y) to F% as the high address of the decryption LDY #LO(F%-1) ; block, so we decrypt to the end of the second block of ; game code at F% (so we decrypt sections ELITE D to ; ELITE K) LDX #KEY2 ; Set X = KEY2 as the decryption seed (the value used to ; encrypt the code, which is done in elite-checksum.py) ; Fall through into DEEORS to decrypt between XX21 and ; $B1FF ; ****************************************************************************** ; ; Name: DEEORS ; Type: Subroutine ; Category: Utility routines ; Summary: Decrypt a multi-page block of memory ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; FRIN(1 0) The start address of the block to decrypt ; ; (A Y) The end address of the block to decrypt ; ; X The decryption seed ; ; ****************************************************************************** .DEEORS STX T ; Store the decryption seed in T as our starting point STA SC+1 ; Set SC(1 0) = (A 0) to point to the start of page A, LDA #0 ; so we can use SC(1 0) + Y as our pointer to the next STA SC ; byte to decrypt .DEEORL LDA (SC),Y ; Set A to the Y-th byte of SC(1 0) SEC ; Set A = A - T SBC T STA (SC),Y ; Update the Y-th byte of SC to the new value in A STA T ; Update T with the new value in A TYA ; Set A to the current byte index in Y BNE P%+4 ; If A <> 0 then decrement the high byte of SC(1 0) to DEC SC+1 ; point to the previous page DEY ; Decrement the byte pointer CPY FRIN ; Loop back to decrypt the next byte, until Y = the low BNE DEEORL ; byte of FRIN(1 0), at which point we have decrypted a ; whole page LDA SC+1 ; Check whether SC(1 0) matches FRIN(1 0) and loop back CMP FRIN+1 ; to decrypt the next byte until it does, at which point BNE DEEORL ; we have decrypted the whole block RTS ; Return from the subroutine EQUB $B7, $AA ; These bytes appear to be unused, though there is a EQUB $45, $23 ; comment in the original source that says "red ; herring", so this would appear to be a red herring ; aimed at confusing any crackers ; ****************************************************************************** ; ; Name: G% ; Type: Variable ; Category: Utility routines ; Summary: Denotes the start of the main game code, from ELITE A to ELITE K ; ; ****************************************************************************** .G% SKIP 0 ; The game code is scrambled from here to F% (or, as the ; original source code puts it, "mutilated") ; ****************************************************************************** ; ; Name: DOENTRY ; Type: Subroutine ; Category: Flight ; Summary: Dock at the space station and work out any mission progression ; Deep dive: The Constrictor mission ; The Thargoid Plans mission ; The Trumbles mission ; ; ****************************************************************************** .DOENTRY JSR RES2 ; Reset a number of flight variables and workspaces JSR LAUN ; Show the space station docking tunnel LDA #0 ; Reduce the speed to 0 STA DELTA ;STA ALPHA ; These instructions are commented out in the original ;STA BETA ; source ;STA ALP1 ;STA BET1 STA GNTMP ; Cool down the lasers completely STA QQ22+1 ; Reset the on-screen hyperspace counter LDA #$FF ; Recharge the forward and aft shields STA FSH STA ASH STA ENERGY ; Recharge the energy banks ;JSR HALL ; This instruction is commented out in the original ; source LDY #44 ; Wait for 44/50 of a second (0.88 seconds) on PAL JSR DELAY ; systems, or 44/60 of a second (0.73 seconds) on NTSC LDA TP ; Fetch bits 0 and 1 of TP, and if they are non-zero AND #%00000011 ; (i.e. mission 1 is either in progress or has been BNE EN1 ; completed), skip to EN1 LDA TALLY+1 ; If the high byte of TALLY is zero (so we have a combat BEQ EN4 ; rank below Competent, or we are Competent but have not ; yet earned a grand total of at least 256 kill points), ; jump to EN4 as we are not yet good enough to qualify ; for a mission LDA GCNT ; Fetch the galaxy number into A, and if any of bits 1-7 LSR A ; are set (i.e. A > 1), jump to EN4 as mission 1 can BNE EN4 ; only be triggered in the first two galaxies JMP BRIEF ; If we get here then mission 1 hasn't started, we have ; reached a combat rank of at least Competent plus 128 ; kill points, and we are in galaxy 0 or 1 (shown ; in-game as galaxy 1 or 2), so it's time to start ; mission 1 by calling BRIEF .EN1 ; If we get here then mission 1 is either in progress or ; has been completed CMP #%00000011 ; If bits 0 and 1 are not both set, then jump to EN2 BNE EN2 JMP DEBRIEF ; Bits 0 and 1 are both set, so mission 1 is both in ; progress and has been completed, which means we have ; only just completed it, so jump to DEBRIEF to end the ; mission get our reward .EN2 ; Mission 1 has been completed, so now to check for ; mission 2 LDA GCNT ; Fetch the galaxy number into A CMP #2 ; If this is not galaxy 2 (shown in-game as galaxy 3), BNE EN4 ; jump to EN4 as we can only start mission 2 in the ; third galaxy LDA TP ; Extract bits 0-3 of TP into A AND #%00001111 CMP #%00000010 ; If mission 1 is complete and no longer in progress, BNE EN3 ; and mission 2 is not yet started, then bits 0-3 of TP ; will be %0010, so this jumps to EN3 if this is not the ; case LDA TALLY+1 ; If the high byte of TALLY is < 5 (so we have a combat CMP #5 ; rank that is less than 3/8 of the way from Dangerous BCC EN4 ; to Deadly), jump to EN4 as our rank isn't high enough ; for mission 2 JMP BRIEF2 ; If we get here, mission 1 is complete and no longer in ; progress, mission 2 hasn't started, we have reached a ; combat rank of 3/8 of the way from Dangerous to ; Deadly, and we are in galaxy 2 (shown in-game as ; galaxy 3), so it's time to start mission 2 by calling ; BRIEF2 .EN3 CMP #%00000110 ; If mission 1 is complete and no longer in progress, BNE EN5 ; and mission 2 has started but we have not yet been ; briefed and picked up the plans, then bits 0-3 of TP ; will be %0110, so this jumps to EN5 if this is not the ; case LDA QQ0 ; Set A = the current system's galactic x-coordinate CMP #215 ; If A <> 215 then jump to EN4 BNE EN4 LDA QQ1 ; Set A = the current system's galactic y-coordinate CMP #84 ; If A <> 84 then jump to EN4 BNE EN4 JMP BRIEF3 ; If we get here, mission 1 is complete and no longer in ; progress, mission 2 has started but we have not yet ; picked up the plans, and we have just arrived at ; Ceerdi at galactic coordinates (215, 84), so we jump ; to BRIEF3 to get a mission brief and pick up the plans ; that we need to carry to Birera .EN5 CMP #%00001010 ; If mission 1 is complete and no longer in progress, BNE EN4 ; and mission 2 has started and we have picked up the ; plans, then bits 0-3 of TP will be %1010, so this ; jumps to EN5 if this is not the case LDA QQ0 ; Set A = the current system's galactic x-coordinate CMP #63 ; If A <> 63 then jump to EN4 BNE EN4 LDA QQ1 ; Set A = the current system's galactic y-coordinate CMP #72 ; If A <> 72 then jump to EN4 BNE EN4 JMP DEBRIEF2 ; If we get here, mission 1 is complete and no longer in ; progress, mission 2 has started and we have picked up ; the plans, and we have just arrived at Birera at ; galactic coordinates (63, 72), so we jump to DEBRIEF2 ; to end the mission and get our reward .EN4 LDA CASH+2 ; If the third most significant byte of CASH(0 1 2 3) CMP #$C4 ; is less than $C4 then the cash amount is less than BCC EN6 ; $C400 (5017.6 credits), so jump to EN6 LDA TP ; If bit 4 of TP is set, then the Trumbles mission has AND #%00010000 ; already been completed, so jump to EN6 BNE EN6 ; If we get here then cheat mode has not been applied, ; we have at least 6553.6 credits and the Trumble ; mission has not yet been offered, so we do that now JMP TBRIEF ; Jump to TBRIEF to offer the Trumble mission, returning ; from the subroutine using a tail call .EN6 JMP BAY ; If we get here them we didn't start or any missions, ; so jump to BAY to go to the docking bay (i.e. show the ; Status Mode screen) ; ****************************************************************************** ; ; Name: BRKBK ; Type: Subroutine ; Category: Save and load ; Summary: Set the standard BRKV handler for the game ; ; ------------------------------------------------------------------------------ ; ; This routine is unused in this version of Elite (it is left over from the ; 6502 Second Processor version). ; ; ****************************************************************************** .BRKBK LDA #LO(BRBR) ; Set BRKV to point to the BRBR routine, disabling SEI ; interrupts while we make the change and re-enabling STA BRKV ; them once we are done LDA #HI(BRBR) STA BRKV+1 CLI RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TRIBDIR ; Type: Variable ; Category: Missions ; Summary: The low byte of the four 16-bit directions in which Trumble ; sprites can move ; ; ****************************************************************************** .TRIBDIR EQUB 0 ; Four directions: 0, 1, -1, 0 EQUB 1 EQUB $FF EQUB 0 ; ****************************************************************************** ; ; Name: TRIBDIRH ; Type: Variable ; Category: Missions ; Summary: The high byte of the four 16-bit directions in which Trumble ; sprites can move ; ; ****************************************************************************** .TRIBDIRH EQUB 0 ; Four directions: 0, 1, -1, 0 EQUB 0 EQUB $FF EQUB 0 ; ****************************************************************************** ; ; Name: SPMASK ; Type: Variable ; Category: Missions ; Summary: Masks for updating sprite bits in VIC+$10 for the top bit of the ; 9-bit x-coordinates of the Trumble sprites ; ; ****************************************************************************** .SPMASK EQUB %11111011 ; Mask for clearing the VIC+$10 bit for sprite 2 ; (Trumble 0) EQUB %00000100 ; Mask for setting the VIC+$10 bit for sprite 2 ; (Trumble 0) EQUB %11110111 ; Mask for clearing the VIC+$10 bit for sprite 3 ; (Trumble 1) EQUB %00001000 ; Mask for setting the VIC+$10 bit for sprite 3 ; (Trumble 1) EQUB %11101111 ; Mask for clearing the VIC+$10 bit for sprite 4 ; (Trumble 2) EQUB %00010000 ; Mask for setting the VIC+$10 bit for sprite 4 ; (Trumble 2) EQUB %11011111 ; Mask for clearing the VIC+$10 bit for sprite 5 ; (Trumble 3) EQUB %00100000 ; Mask for setting the VIC+$10 bit for sprite 5 ; (Trumble 3) EQUB %10111111 ; Mask for clearing the VIC+$10 bit for sprite 6 ; (Trumble 4) EQUB %01000000 ; Mask for setting the VIC+$10 bit for sprite 6 ; (Trumble 4) EQUB %01111111 ; Mask for clearing the VIC+$10 bit for sprite 7 ; (Trumble 5) EQUB %10000000 ; Mask for setting the VIC+$10 bit for sprite 7 ; (Trumble 5) ; ****************************************************************************** ; ; Name: MVTRIBS ; Type: Subroutine ; Category: Missions ; Summary: Move the Trumble sprites around on-screen ; Deep dive: Sprite usage in Commodore 64 Elite ; ; ****************************************************************************** .MVTRIBS LDA MCNT ; We want to move one Trumble sprite on each iteration AND #7 ; around the main loop, so set A to the main loop ; counter mod 8, so A counts up from 0 to 7 and repeats ; as we iterate around the main loop CMP TRIBCT ; If A < TRIBCT then skip the following instruction BCC P%+5 JMP NOMVETR ; Jump to NOMVETR to return to the main game loop ; without moving any sprites ;STA T ; This instruction is commented out in the original ; source ; TRIBCT contains the number of Trumble sprites being ; shown on-screen, in the range 0 to 6, and we only ; call MVTRIBS when TRIBCT is non-zero, so it must be ; in the range 1 to 6 ; ; We also know that A < TRIBCT, so if we get here then ; we know A must be in the range 0 to TRIBCT - 1, with ; a maximum value of 5 ; ; We can therefore move Trumble number A, and this will ; ensure we work through the visible Trumble sprites, ; updating one per iteration, with each sprite being ; moved every eight iterations around the main loop ASL A ; Set Y = A * 2 so we can use it as an index into the TAY ; two-byte tables at TRIBVX, TRIBVXH and TRIBXH for the ; Trumble sprite that we are processing ; ; As A is reused and we use Y as the index into the data ; for this Trumble, will refer to "Trumble Y" and ; "Trumble sprite Y" in the following) ; ; Note that Trumble Y uses sprite Y + 2, as sprites 0 ; and 1 have other uses (they contain the laser sight ; sprite and the explosion sprite respectively) LDA #%101 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 1 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on ; ; See the memory map at the top of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore JSR DORND ; Set A and X to random numbers CMP #235 ; If the random number in A < 235 (92% chance), jump to BCC MVTR1 ; MVTR1 to skip the following, so we only change the ; direction of movement for this Trumble sprite 8% of ; the time (and we keep the existing direction for the ; other 92%) AND #3 ; Set X to the random number in A, reduced to the range TAX ; 0 to 3 so we can pick one of four directions to move ; in LDA TRIBDIR,X ; Set (TRIBVXH TRIBVX) for Trumble sprite Y to the X-th STA TRIBVX,Y ; entry from the TRIBDIRH and TRIBDIR tables LDA TRIBDIRH,X ; STA TRIBVXH,Y ; These tables contain four 16-bit directions, so this ; randomly sets (TRIBVXH TRIBVX) to 0, 1, -1 or 0 (so ; there's a 50% chance of no horizontal movement, and a ; 25% chance of movement left or right) ; ; (TRIBVXH TRIBVX) now contains the updated x-axis ; velocity of Trumble Y, i.e. the amount that it moves ; horizontally in this iteration of the main loop JSR DORND ; Set A and X to random numbers AND #3 ; Set X to the random number in A, reduced to the range TAX ; 0 to 3 so we can pick another one of four directions ; to move in LDA TRIBDIR,X ; Set TRIBVX+1 for Trumble sprite Y to the X-th entry STA TRIBVX+1,Y ; from the TRIBDIR table (so there's a 50% chance of no ; vertical movement, and a 25% chance of movement up or ; down) ; ; TRIBVX+1 now contains the updated y-axis velocity of ; Trumble Y, i.e. the amount that it moves vertically in ; this iteration of the main loop .MVTR1 ; We now move Trumble sprite Y by applying the following ; velocities: ; ; * Apply (TRIBVXH TRIBVX) to the x-coordinate ; ; * Apply TRIBVX+1 to the y-coordinate ; ; The second calculation is pretty easy, but the first ; one is complicated by the fact that x-coordinates are ; 9-bit values with the top bits stored in VIC+$10 LDA SPMASK,Y ; Clear the relevant bit for this sprite in VIC+$10 AND VIC+$10 ; STA VIC+$10 ; We will replace it with a set bit after calculating ; the new x-coordinate below, if we need to LDA VIC+5,Y ; Add TRIBVX+1 for Trumble Y to the y-coordinate of CLC ; Trumble sprite Y, by updating VIC+$05 + Y ADC TRIBVX+1,Y ; STA VIC+5,Y ; This works because VIC+$05 contains the y-coordinate ; for sprite 2, the first Trumble sprite, and the ; y-coordinates for sprites 3 onwards are in VIC+$07, ; VIC+$09 and so on ; ; We don't worry about whether the addition overflows, ; so Trumbles that move off the top or bottom of the ; screen simply reappear on the opposite side ; We now want to add (TRIBVXH TRIBVX) to the sprite's ; x-coordinate, as follows: ; ; (A T) = (TRIBXH VIC+$04) + (TRIBVXH TRIBVX) ; ; The table at TRIBXH is used to store the top bit of ; the sprite's 9-bit x-coordinate ; ; Now to do the above calculation, one byte at a time CLC ; Set T = VIC+$04 + TRIBVX LDA VIC+4,Y ; ADC TRIBVX,Y ; We do this for Trumble sprite Y, so this does the low STA T ; byte of the calculation ; ; This works because VIC+$04 contains the x-coordinate ; for sprite 2, the first Trumble sprite, and the ; x-coordinates for sprites 3 onwards are in VIC+$06, ; VIC+$08 and so on LDA TRIBXH,Y ; Set A = TRIBXH + TRIBVXH ADC TRIBVXH,Y ; ; So this does the high byte of the calculation ; (A T) now contains the updated x-coordinate of Trumble ; sprite Y BPL nominus ; If A is positive then the Trumble has not just moved ; off the left edge of the screen, so jump to nominus LDA #$48 ; The Trumble has just moved off the left edge of the STA T ; screen, so set (A T) = $148 = 328 to move it to the LDA #$01 ; right edge of the screen .nominus AND #1 ; If bit 0 of A is zero, then bit 9 of the x-coordinate BEQ oktrib ; in (A T) is zero, so jump to oktrib with A set to 0 ; to store a 0 in TRIBXH (which is where we store the ; top coordinate of the x-coordinate) and set the ; sprite's y-coordinate to (A T) ; If we get here then (A T) has bit 9 set, so it's of ; the form $1xx (where xx is T) and we need to check ; whether it's off the right edge of the screen LDA T ; If T < $50, then (A T) < $150 = 336, so the sprite has CMP #$50 ; not moved off the right edge of the screen LDA #1 ; BCC oktrib ; So set A = 1 and jump to oktrib to store a 1 in TRIBXH ; (which is where we store the top coordinate of the ; x-coordinate) and set the sprite's y-coordinate to ; (A T) LDA #0 ; If we get here then the Trumble has just moved off the STA T ; right edge of the screen, so set (A T) = 0 to move it ; to the left edge of the screen .oktrib STA TRIBXH,Y ; Store the updated top byte of (A T) in TRIBXH so we ; can use it the next time we move this Trumble sprite ; We now set the sprite's x-coordinate to (A T) by ; setting the relevant bit in VIC+$10 to the top bit in ; bit 0 of A, and setting the relevant x-coordinate ; register from VIC+$04 onwards to the low byte of (A T) BEQ NOHIBIT ; If the top byte is zero then jump to NOHIBIT to skip ; updating the top bit of the sprite's x-coordinate, as ; we already zeroed the relevant bit in VIC+$10 above LDA SPMASK+1,Y ; Set the relevant bit for this sprite in VIC+$10, ORA VIC+$10 ; making sure to disable interrupts while we do SEI STA VIC+$10 .NOHIBIT LDA T ; Set the bottom 8-bits of the x-coordinate of Trumble STA VIC+4,Y ; sprite Y to the low byte of (A T), i.e. T, by setting ; VIC+$04 + Y to T CLI ; Enable interrupts again, in case we disabled them ; above LDA #%100 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore JMP NOMVETR ; Jump to NOMVETR to return to the main game loop ; ****************************************************************************** ; ; Name: Main flight loop (Part 1 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: Seed the random number generator ; Deep dive: Program flow of the main game loop ; Generating random numbers ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Seed the random number generator ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; M% The entry point for the main flight loop ; ; NOMVETR The re-entry point in the main game loop for when there ; are no sprites to move ; ; ****************************************************************************** .M% LDA K% ; We want to seed the random number generator with a ; pretty random number, so fetch the contents of K%, ; which is the x_lo coordinate of the planet. This value ; will be fairly unpredictable, so it's a pretty good ; candidate STA RAND ; Store the seed in the first byte of the four-byte ; random number seed that's stored in RAND LDA TRIBCT ; If TRIBCT is non-zero then we have some Trumbles in BEQ NOMVETR ; the hold, so jump to MVTRIBS to move their sprites JMP MVTRIBS ; around the screen, if applicable ; ; The MVTRIBS routine jumps back to NOMVETR when it has ; finished moving Trumbles around .NOMVETR ; ****************************************************************************** ; ; Name: Main flight loop (Part 2 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: Calculate the alpha and beta angles from the current pitch and ; roll of our ship ; Deep dive: Program flow of the main game loop ; Pitching and rolling ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Calculate the alpha and beta angles from the current pitch and roll ; ; Here we take the current rate of pitch and roll, as set by the joystick or ; keyboard, and convert them into alpha and beta angles that we can use in the ; matrix functions to rotate space around our ship. The alpha angle covers ; roll, while the beta angle covers pitch (there is no yaw in this version of ; Elite). The angles are in radians, which allows us to use the small angle ; approximation when moving objects in the sky (see the MVEIT routine for more ; on this). Also, the signs of the two angles are stored separately, in both ; the sign and the flipped sign, as this makes calculations easier. ; ; ****************************************************************************** LDX JSTX ; Set X to the current rate of roll in JSTX JSR cntr ; Apply keyboard damping twice (if enabled) so the roll JSR cntr ; rate in X creeps towards the centre by 2 ; The roll rate in JSTX increases if we press ">" (and ; the RL indicator on the dashboard goes to the right) ; ; This rolls our ship to the right (clockwise), but we ; actually implement this by rolling everything else ; to the left (anti-clockwise), so a positive roll rate ; in JSTX translates to a negative roll angle alpha TXA ; Set A and Y to the roll rate but with the sign bit EOR #%10000000 ; flipped (i.e. set them to the sign we want for alpha) TAY AND #%10000000 ; Extract the flipped sign of the roll rate and store STA ALP2 ; in ALP2 (so ALP2 contains the sign of the roll angle ; alpha) STX JSTX ; Update JSTX with the damped value that's still in X EOR #%10000000 ; Extract the correct sign of the roll rate and store STA ALP2+1 ; in ALP2+1 (so ALP2+1 contains the flipped sign of the ; roll angle alpha) TYA ; Set A to the roll rate but with the sign bit flipped BPL P%+7 ; If the value of A is positive, skip the following ; three instructions EOR #%11111111 ; A is negative, so change the sign of A using two's CLC ; complement so that A is now positive and contains ADC #1 ; the absolute value of the roll rate, i.e. |JSTX| LSR A ; Divide the (positive) roll rate in A by 4 LSR A CMP #8 ; If A >= 8, skip the following instruction BCS P%+3 LSR A ; A < 8, so halve A again STA ALP1 ; Store A in ALP1, so we now have: ; ; ALP1 = |JSTX| / 8 if |JSTX| < 32 ; ; ALP1 = |JSTX| / 4 if |JSTX| >= 32 ; ; This means that at lower roll rates, the roll angle is ; reduced closer to zero than at higher roll rates, ; which gives us finer control over the ship's roll at ; lower roll rates ; ; Because JSTX is in the range -127 to +127, ALP1 is ; in the range 0 to 31 ORA ALP2 ; Store A in ALPHA, but with the sign set to ALP2 (so STA ALPHA ; ALPHA has a different sign to the actual roll rate) LDX JSTY ; Set X to the current rate of pitch in JSTY JSR cntr ; Apply keyboard damping so the pitch rate in X creeps ; towards the centre by 1 TXA ; Set A and Y to the pitch rate but with the sign bit EOR #%10000000 ; flipped TAY AND #%10000000 ; Extract the flipped sign of the pitch rate into A STX JSTY ; Update JSTY with the damped value that's still in X STA BET2+1 ; Store the flipped sign of the pitch rate in BET2+1 EOR #%10000000 ; Extract the correct sign of the pitch rate and store STA BET2 ; it in BET2 TYA ; Set A to the pitch rate but with the sign bit flipped BPL P%+4 ; If the value of A is positive, skip the following ; instruction EOR #%11111111 ; A is negative, so flip the bits ADC #4 ; Add 4 to the (positive) pitch rate, so the maximum ; value is now up to 131 (rather than 127) LSR A ; Divide the (positive) pitch rate in A by 16 LSR A LSR A LSR A CMP #3 ; If A >= 3, skip the following instruction BCS P%+3 LSR A ; A < 3, so halve A again STA BET1 ; Store A in BET1, so we now have: ; ; BET1 = |JSTY| / 32 if |JSTY| < 48 ; ; BET1 = |JSTY| / 16 if |JSTY| >= 48 ; ; This means that at lower pitch rates, the pitch angle ; is reduced closer to zero than at higher pitch rates, ; which gives us finer control over the ship's pitch at ; lower pitch rates ; ; Because JSTY is in the range -131 to +131, BET1 is in ; the range 0 to 8 ORA BET2 ; Store A in BETA, but with the sign set to BET2 (so STA BETA ; BETA has the same sign as the actual pitch rate) ; ****************************************************************************** ; ; Name: Main flight loop (Part 3 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: Scan for flight keys and process the results ; Deep dive: Program flow of the main game loop ; The key logger ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Scan for flight keys and process the results ; ; Flight keys are logged in the key logger at location KY1 onwards, with a ; non-zero value in the relevant location indicating a key press. ; ; The key presses that are processed are as follows: ; ; * Space and "?" to speed up and slow down ; * "U", "T" and "M" to unarm, target and fire missiles ; * "C=" to fire an energy bomb ; * Left arrow to launch an escape pod ; * "J" to initiate an in-system jump ; * "E" to deploy E.C.M. anti-missile countermeasures ; * "C" to use the docking computer ; * "A" to fire lasers ; ; ****************************************************************************** LDA KY2 ; If Space is being pressed, keep going, otherwise jump BEQ MA17 ; down to MA17 to skip the following LDA DELTA ; The "go faster" key is being pressed, so first we CMP #40 ; fetch the current speed from DELTA into A, and if BCS MA17 ; A >= 40, we are already going at full pelt, so jump ; down to MA17 to skip the following INC DELTA ; We can go a bit faster, so increment the speed in ; location DELTA .MA17 LDA KY1 ; If "?" is being pressed, keep going, otherwise jump BEQ MA4 ; down to MA4 to skip the following DEC DELTA ; The "slow down" key is being pressed, so we decrement ; the current ship speed in DELTA BNE MA4 ; If the speed is still greater than zero, jump to MA4 INC DELTA ; Otherwise we just braked a little too hard, so bump ; the speed back up to the minimum value of 1 .MA4 LDA KY15 ; If "U" is being pressed and the number of missiles AND NOMSL ; in NOMSL is non-zero, keep going, otherwise jump down BEQ MA20 ; to MA20 to skip the following LDY #GREEN2 ; The "unarm missiles" key is being pressed, so call JSR ABORT ; ABORT to unarm the missile and update the missile ; indicators on the dashboard to green (Y = $EE) LDY #sfxboop ; Call the NOISE routine with Y = sfxboop to make a low, JSR NOISE ; long beep to indicate the missile is now unarmed LDA #0 ; Set MSAR to 0 to indicate that no missiles are STA MSAR ; currently armed .MA20 LDA MSTG ; If MSTG is positive (i.e. it does not have bit 7 set), BPL MA25 ; then it indicates we already have a missile locked on ; a target (in which case MSTG contains the ship number ; of the target), so jump to MA25 to skip targeting. Or ; to put it another way, if MSTG = $FF, which means ; there is no current target lock, keep going LDA KY14 ; If "T" is being pressed, keep going, otherwise jump BEQ MA25 ; down to MA25 to skip the following LDX NOMSL ; If the number of missiles in NOMSL is zero, jump down BEQ MA25 ; to MA25 to skip the following STA MSAR ; The "target missile" key is being pressed and we have ; at least one missile, so set MSAR = $FF to denote that ; our missile is currently armed (we know A has the ; value $FF, as we just loaded it from MSTG and checked ; that it was negative) LDY #YELLOW2 ; Change the leftmost missile indicator to yellow JSR MSBAR ; on the missile bar (this call changes the leftmost ; indicator because we set X to the number of missiles ; in NOMSL above, and the indicators are numbered from ; right to left, so X is the number of the leftmost ; indicator) .MA25 LDA KY16 ; If "M" is being pressed, keep going, otherwise jump BEQ MA24 ; down to MA24 to skip the following LDA MSTG ; If MSTG = $FF then there is no target lock, so jump to BMI MA64 ; MA64 to skip the following (also skipping the checks ; for "C=", left arrow, "J" and "E") JSR FRMIS ; The "fire missile" key is being pressed and we have ; a missile lock, so call the FRMIS routine to fire ; the missile .MA24 LDA KY12 ; If "C=" is being pressed, keep going, otherwise jump BEQ MA76 ; down to MA76 to skip the following ASL BOMB ; The "energy bomb" key is being pressed, so double ; the value in BOMB. If we have an energy bomb fitted, ; BOMB will contain $7F (%01111111) before this shift ; and will contain $FE (%11111110) after the shift; if ; we don't have an energy bomb fitted, BOMB will still ; contain 0. The bomb explosion is dealt with in the ; MAL1 routine below - this just registers the fact that ; we've set the bomb ticking BEQ MA76 ; If BOMB now contains 0, then the bomb is not going off ; any more (or it never was), so skip the following ; instruction LDY #%11010000 ; Set bit 4 of moonflower so the screen jumps from STY moonflower ; standard bitmap mode to multicolour bitmap mode, as ; the IRQ handler sets VIC+$16 to the value of ; moonflower, and bit 4 of VIC+$16 controls multicolour ; mode ; ; This makes the space view turn into a coloured mess of ; double-width pixels while the energy bomb is going off LDY #sfxbomb ; Call the NOISE routine with Y = sfxbomb to make the JSR NOISE ; sound of the energy bomb going off .MA76 LDA KY20 ; If "P" is being pressed, keep going, otherwise skip BEQ MA78 ; the next two instructions LDA #0 ; The "cancel docking computer" key is bring pressed, STA auto ; so turn it off by setting auto to 0 JSR stopbd ; Stop playing the docking music (if it is playing) .MA78 LDA KY13 ; If ESCAPE is being pressed and we have an escape pod AND ESCP ; fitted, keep going, otherwise jump to noescp to skip BEQ noescp ; the following instructions LDA MJ ; If we are in witchspace, we can't launch our escape BNE noescp ; pod, so jump down to noescp JMP ESCAPE ; The button is being pressed to launch an escape pod ; and we have an escape pod fitted, so jump to ESCAPE to ; launch it, and exit the main flight loop using a tail ; call .noescp LDA KY18 ; If "J" is being pressed, keep going, otherwise skip BEQ P%+5 ; the next instruction JSR WARP ; Call the WARP routine to do an in-system jump LDA KY17 ; If "E" is being pressed and we have an E.C.M. fitted, AND ECM ; keep going, otherwise jump down to MA64 to skip the BEQ MA64 ; following LDA ECMA ; If ECMA is non-zero, that means an E.C.M. is already BNE MA64 ; operating and is counting down (this can be either ; our E.C.M. or an opponent's), so jump down to MA64 to ; skip the following (as we can't have two E.C.M. ; systems operating at the same time) DEC ECMP ; The E.C.M. button is being pressed and nobody else ; is operating their E.C.M., so decrease the value of ; ECMP to make it non-zero, to denote that our E.C.M. ; is now on JSR ECBLB2 ; Call ECBLB2 to light up the E.C.M. indicator bulb on ; the dashboard, set the E.C.M. countdown timer to 32, ; and start making the E.C.M. sound .MA64 LDA KY19 ; If "C" is being pressed, and we have a docking AND DKCMP ; computer fitted, keep going, otherwise jump down to BEQ MA68 ; MA68 to skip the following EOR KLO+$29 ; If "X" is also being pressed, then the "C" we just BEQ MA68 ; detected is likely to be a ghost key press, caused by ; the player actually holding down "A", "S" and "X" ; (i.e. up, down and fire) ; ; Ghost key presses can occur on the Commodore 64 when ; three keys are held down that form a right angle in ; the keyboard scan matrix, in which case a fourth key ; can also appear to be "pressed" ; ; There is a comment in the original source of "kill ; phantom Cs" that seems to confirm this STA auto ; Set auto to the non-zero value of A, so the docking ; computer is activated JSR startbd ; Start playing the docking music .MA68 LDA #0 ; Set LAS = 0, to switch the laser off while we do the STA LAS ; following logic STA DELT4 ; Take the 16-bit value (DELTA 0) - i.e. a two-byte LDA DELTA ; number with DELTA as the high byte and 0 as the low LSR A ; byte - and divide it by 4, storing the 16-bit result ROR DELT4 ; in DELT4(1 0). This has the effect of storing the LSR A ; current speed * 64 in the 16-bit location DELT4(1 0) ROR DELT4 STA DELT4+1 LDA LASCT ; If LASCT is zero, keep going, otherwise the laser is BNE MA3 ; a pulse laser that is between pulses, so jump down to ; MA3 to skip the following LDA KY7 ; If "A" is being pressed, keep going, otherwise jump BEQ MA3 ; down to MA3 to skip the following LDA GNTMP ; If the laser temperature >= 242 then the laser has CMP #242 ; overheated, so jump down to MA3 to skip the following BCS MA3 LDX VIEW ; If the current space view has a laser fitted (i.e. the LDA LASER,X ; laser power for this view is greater than zero), then BEQ MA3 ; keep going, otherwise jump down to MA3 to skip the ; following ; If we get here, then the "fire" button is being ; pressed, our laser hasn't overheated and isn't already ; being fired, and we actually have a laser fitted to ; the current space view, so it's time to hit me with ; those laser beams PHA ; Store the current view's laser power on the stack AND #%01111111 ; Set LAS and LAS2 to bits 0-6 of the laser power STA LAS STA LAS2 ; We now set Y to the correct sound to pass to the NOISE ; routine to make the sound of the laser firing LDY #sfxplas ; Set Y to the sound of a pulse laser firing PLA ; Set A to the current view's laser power, which we PHA ; stored on the stack above (and leave the value on ; the stack) BMI bmorarm ; If A >= 128, jump to bmorarm to check whether this is ; a beam laser or a military laser CMP #Mlas ; If A is not the power for a mining laser, skip the BNE P%+4 ; following instruction (and then jump to custard with ; Y still set to the sound of a pulse laser firing) LDY #sfxmlas ; Set Y to the sound of a mining laser firing BNE custard ; Jump to custard to make the sound in Y, i.e. a pulse ; or mining laser (this BNE is effectively a JMP as ; either Y is never zero, or we jumped here with a BNE) .bmorarm CMP #Armlas ; If this is a military laser, skip the following two BEQ P%+5 ; instructions to set Y to the sound of a military laser LDY #sfxblas ; This is not a military laser, so it must be a beam ; laser, so set Y to the sound of a beam laser firing EQUB $2C ; Skip the next instruction by turning it into ; $2C $A0 $0B, or BIT $0BA0, which does nothing apart ; from affect the flags LDY #sfxalas ; Set Y to the sound of a military laser firing .custard JSR NOISE ; Call NOISE to make the sound of the appropriate laser ; firing (pulse, beam, mining or military) JSR LASLI ; Call LASLI to draw the laser lines PLA ; Restore the current view's laser power into A BPL ma1 ; If the laser power has bit 7 set, then it's an "always ; on" laser rather than a pulsing laser, so keep going, ; otherwise jump down to ma1 to skip the following ; instruction LDA #0 ; This is an "always on" laser (i.e. a beam laser or a ; military laser), so set A = 0, which will be stored in ; LASCT to denote that this is not a pulsing laser .ma1 AND #%11111010 ; LASCT will be set to 0 for beam lasers, and to the STA LASCT ; laser power AND %11111010 for pulse lasers, which ; comes to 10 for pulse lasers (as pulse lasers have a ; power of 15) or 50 for mining lasers (as mining ; lasers hava a power of 50). See MA23 in part 16 for ; more on laser pulsing and LASCT ; ****************************************************************************** ; ; Name: Main flight loop (Part 4 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: For each nearby ship: Copy the ship's data block from K% to the ; zero-page workspace at INWK ; Deep dive: Program flow of the main game loop ; Ship data blocks ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Start looping through all the ships in the local bubble, and for each ; one: ; ; * Copy the ship's data block from K% to INWK ; ; * Set XX0 to point to the ship's blueprint (if this is a ship) ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; MAL1 Marks the beginning of the ship analysis loop, so we ; can jump back here from part 12 of the main flight loop ; to work our way through each ship in the local bubble. ; We also jump back here when a ship is removed from the ; bubble, so we can continue processing from the next ship ; ; ****************************************************************************** .MA3 LDX #0 ; We're about to work our way through all the ships in ; our local bubble of universe, so set a counter in X, ; starting from 0, to refer to each ship slot in turn .MAL1 STX XSAV ; Store the current slot number in XSAV LDA FRIN,X ; Fetch the contents of this slot into A. If it is 0 BNE P%+5 ; then this slot is empty and we have no more ships to JMP MA18 ; process, so jump to MA18 below, otherwise A contains ; the type of ship that's in this slot, so skip over the ; JMP MA18 instruction and keep going STA TYPE ; Store the ship type in TYPE JSR GINF ; Call GINF to fetch the address of the ship data block ; for the ship in slot X and store it in INF. The data ; block is in the K% workspace, which is where all the ; ship data blocks are stored ; Next we want to copy the ship data block from INF to ; the zero-page workspace at INWK, so we can process it ; more efficiently LDY #NI%-1 ; There are NI% bytes in each ship data block (and in ; the INWK workspace, so we set a counter in Y so we can ; loop through them .MAL2 LDA (INF),Y ; Load the Y-th byte of INF and store it in the Y-th STA INWK,Y ; byte of INWK DEY ; Decrement the loop counter BPL MAL2 ; Loop back for the next byte until we have copied the ; last byte from INF to INWK LDA TYPE ; If the ship type is negative then this indicates a BMI MA21 ; planet or sun, so jump down to MA21, as the next bit ; sets up a pointer to the ship blueprint, and then ; checks for energy bomb damage, and neither of these ; apply to planets and suns ASL A ; Set Y = ship type * 2 TAY LDA XX21-2,Y ; The ship blueprints at XX21 start with a lookup STA XX0 ; table that points to the individual ship blueprints, ; so this fetches the low byte of this particular ship ; type's blueprint and stores it in XX0 LDA XX21-1,Y ; Fetch the high byte of this particular ship type's STA XX0+1 ; blueprint and store it in XX0+1 ; ****************************************************************************** ; ; Name: Main flight loop (Part 5 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: For each nearby ship: If an energy bomb has been set off, ; potentially kill this ship ; Deep dive: Program flow of the main game loop ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Continue looping through all the ships in the local bubble, and for each ; one: ; ; * If an energy bomb has been set off and this ship can be killed, kill it ; and increase the kill tally ; ; ****************************************************************************** LDA BOMB ; If we set off our energy bomb (see MA24 above), then BPL MA21 ; BOMB is now negative, so this skips to MA21 if our ; energy bomb is not going off CPY #2*SST ; If the ship in Y is the space station, jump to BA21 BEQ MA21 ; as energy bombs are useless against space stations CPY #2*THG ; If the ship in Y is a Thargoid, jump to BA21 as energy BEQ MA21 ; bombs have no effect against Thargoids CPY #2*CON ; If the ship in Y is the Constrictor, jump to BA21 BCS MA21 ; as energy bombs are useless against the Constrictor ; (the Constrictor is the target of mission 1, and it ; would be too easy if it could just be blown out of ; the sky with a single key press) LDA INWK+31 ; If the ship we are checking has bit 5 set in its ship AND #%00100000 ; byte #31, then it is already exploding, so jump to BNE MA21 ; BA21 as ships can't explode more than once ASL INWK+31 ; The energy bomb is killing this ship, so set bit 7 of SEC ; the ship byte #31 to indicate that it has now been ROR INWK+31 ; killed LDX TYPE ; Set X to the type of the ship that was killed so the ; following call to EXNO2 can award us the correct ; number of fractional kill points JSR EXNO2 ; Call EXNO2 to process the fact that we have killed a ; ship (so increase the kill tally, make an explosion ; sound and possibly display "RIGHT ON COMMANDER!") ; ****************************************************************************** ; ; Name: Main flight loop (Part 6 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: For each nearby ship: Move the ship in space and copy the updated ; INWK data block back to K% ; Deep dive: Program flow of the main game loop ; Program flow of the ship-moving routine ; Ship data blocks ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Continue looping through all the ships in the local bubble, and for each ; one: ; ; * Move the ship in space ; ; * Copy the updated ship's data block from INWK back to K% ; ; ****************************************************************************** .MA21 JSR MVEIT ; Call MVEIT to move the ship we are processing in space ; Now that we are done processing this ship, we need to ; copy the ship data back from INWK to the correct place ; in the K% workspace. We already set INF in part 4 to ; point to the ship's data block in K%, so we can simply ; do the reverse of the copy we did before, this time ; copying from INWK to INF LDY #NI%-1 ; Set a counter in Y so we can loop through the NI% ; bytes in the ship data block .MAL3 LDA INWK,Y ; Load the Y-th byte of INWK and store it in the Y-th STA (INF),Y ; byte of INF DEY ; Decrement the loop counter BPL MAL3 ; Loop back for the next byte, until we have copied the ; last byte from INWK back to INF ; ****************************************************************************** ; ; Name: Main flight loop (Part 7 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: For each nearby ship: Check whether we are docking, scooping or ; colliding with it ; Deep dive: Program flow of the main game loop ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Continue looping through all the ships in the local bubble, and for each ; one: ; ; * Check how close we are to this ship and work out if we are docking, ; scooping or colliding with it ; ; ****************************************************************************** LDA INWK+31 ; Fetch the status of this ship from bits 5 (is ship AND #%10100000 ; exploding?) and bit 7 (has ship been killed?) from ; ship byte #31 into A JSR MAS4 ; Or this value with x_hi, y_hi and z_hi BNE MA65 ; If this value is non-zero, then either the ship is ; far away (i.e. has a non-zero high byte in at least ; one of the three axes), or it is already exploding, ; or has been flagged as being killed - in which case ; jump to MA65 to skip the following, as we can't dock ; scoop or collide with it LDA INWK ; Set A = (x_lo OR y_lo OR z_lo), and if bit 7 of the ORA INWK+3 ; result is set, the ship is still a fair distance ORA INWK+6 ; away (further than 127 in at least one axis), so jump BMI MA65 ; to MA65 to skip the following, as it's too far away to ; dock, scoop or collide with LDX TYPE ; If the current ship type is negative then it's either BMI MA65 ; a planet or a sun, so jump down to MA65 to skip the ; following, as we can't dock with it or scoop it CPX #SST ; If this ship is the space station, jump to ISDK to BEQ ISDK ; check whether we are docking with it AND #%11000000 ; If bit 6 of (x_lo OR y_lo OR z_lo) is set, then the BNE MA65 ; ship is still a reasonable distance away (further than ; 63 in at least one axis), so jump to MA65 to skip the ; following, as it's too far away to dock, scoop or ; collide with CPX #MSL ; If this ship is a missile, jump down to MA65 to skip BEQ MA65 ; the following, as we can't scoop or dock with a ; missile, and it has its own dedicated collision ; checks in the TACTICS routine LDA BST ; If we have fuel scoops fitted then BST will be $FF, ; otherwise it will be 0 AND INWK+5 ; Ship byte #5 contains the y_sign of this ship, so a ; negative value here means the canister is below us, ; which means the result of the AND will be negative if ; the canister is below us and we have a fuel scoop ; fitted BPL MA58 ; If the result is positive, then we either have no ; scoop or the canister is above us, and in both cases ; this means we can't scoop the item, so jump to MA58 ; to process a collision ; ****************************************************************************** ; ; Name: Main flight loop (Part 8 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: For each nearby ship: Process us potentially scooping this item ; Deep dive: Program flow of the main game loop ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Continue looping through all the ships in the local bubble, and for each ; one: ; ; * Process us potentially scooping this item ; ; ****************************************************************************** CPX #OIL ; If this is a cargo canister, jump to oily to randomly BEQ oily ; decide the canister's contents LDY #0 ; Fetch byte #0 of the ship's blueprint LDA (XX0),Y LSR A ; Shift it right four times, so A now contains the high LSR A ; nibble (i.e. bits 4-7) LSR A LSR A BEQ MA58 ; If A = 0, jump to MA58 to skip all the docking and ; scooping checks ; Only the Thargon, alloy plate, splinter and escape pod ; have non-zero high nibbles in their blueprint byte #0 ; so if we get here, our ship is one of those, and the ; high nibble gives the market item number of the item ; when scooped, less 1 ADC #1 ; Add 1 to the high nibble to get the market item ; number BNE slvy2 ; Skip to slvy2 so we scoop the ship as a market item .oily JSR DORND ; Set A and X to random numbers and reduce A to a AND #7 ; random number in the range 0-7 .slvy2 ; By the time we get here, we are scooping, and A ; contains the type of item we are scooping (a random ; number 0-7 if we are scooping a cargo canister, 3 if ; we are scooping an escape pod, or 16 if we are ; scooping a Thargon). These numbers correspond to the ; relevant market items (see QQ23 for a list), so a ; cargo canister can contain anything from food to ; computers, while escape pods contain slaves, and ; Thargons become alien items when scooped JSR tnpr1 ; Call tnpr1 with the scooped cargo type stored in A ; to work out whether we have room in the hold for one ; tonne of this cargo (A is set to 1 by this call, and ; the C flag contains the result) LDY #78 ; This instruction has no effect, so presumably it used ; to do something, but didn't get removed BCS MA59 ; If the C flag is set then we have no room in the hold ; for the scooped item, so jump down to MA59 make a ; sound to indicate failure, before destroying the ; canister LDY QQ29 ; Scooping was successful, so set Y to the type of ; item we just scooped, which we stored in QQ29 above ADC QQ20,Y ; Add A (which we set to 1 above) to the number of items STA QQ20,Y ; of type Y in the cargo hold, as we just successfully ; scooped one canister of type Y TYA ; Print recursive token 48 + Y as an in-flight token, ADC #208 ; which will be in the range 48 ("FOOD") to 64 ("ALIEN JSR MESS ; ITEMS"), so this prints the scooped item's name ASL NEWB ; The item has now been scooped, so set bit 7 of its SEC ; NEWB flags to indicate this ROR NEWB .MA65 JMP MA26 ; If we get here, then the ship we are processing was ; too far away to be scooped, docked or collided with, ; so jump to MA26 to skip over the collision routines ; and move on to missile targeting ; ****************************************************************************** ; ; Name: Main flight loop (Part 9 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: For each nearby ship: If it is a space station, check whether we ; are successfully docking with it ; Deep dive: Program flow of the main game loop ; Docking checks ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Process docking with a space station ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; GOIN We jump here from part 3 of the main flight loop if the ; docking computer is activated by pressing "C" ; ; ****************************************************************************** .ISDK LDA K%+NI%+36 ; 1. Fetch the NEWB flags (byte #36) of the second ship AND #%00000100 ; in the ship data workspace at K%, which is reserved BNE MA62 ; for the sun or the space station (in this case it's ; the latter), and if bit 2 is set, meaning the station ; is hostile, jump down to MA62 to fail docking (so ; trying to dock at a station that we have annoyed does ; not end well) LDA INWK+14 ; 2. If nosev_z_hi < 214, jump down to MA62 to fail CMP #214 ; docking, as the angle of approach is greater than 26 BCC MA62 ; degrees JSR SPS1 ; Call SPS1 to calculate the vector to the planet and ; store it in XX15 LDA XX15+2 ; Set A to the z-axis of the vector ; This version of Elite omits check 3 (which would check ; the sign of the z-axis) CMP #89 ; 4. If z-axis < 89, jump to MA62 to fail docking, as BCC MA62 ; we are not in the 22.0 degree safe cone of approach LDA INWK+16 ; 5. If |roofv_x_hi| < 80, jump to MA62 to fail docking, AND #%01111111 ; as the slot is more than 36.6 degrees from horizontal CMP #80 BCC MA62 .GOIN ; If we arrive here, we just docked successfully JSR stopbd ; Stop playing the docking music (if it is playing) JMP DOENTRY ; Go to the docking bay (i.e. show the Status Mode ; screen) .MA62 ; If we arrive here, docking has just failed LDA DELTA ; If the ship's speed is < 5, jump to MA67 to register CMP #5 ; some damage, but not a huge amount BCC MA67 JMP DEATH ; Otherwise we have just crashed into the station, so ; process our death ; ****************************************************************************** ; ; Name: Main flight loop (Part 10 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: For each nearby ship: Remove if scooped, or process collisions ; Deep dive: Program flow of the main game loop ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Continue looping through all the ships in the local bubble, and for each ; one: ; ; * Remove scooped item after both successful and failed scooping attempts ; ; * Process collisions ; ; ****************************************************************************** .MA59 ; If we get here then scooping failed JSR EXNO3 ; Make the sound of the cargo canister being destroyed ; and fall through into MA60 to remove the canister ; from our local bubble .MA60 ; If we get here then scooping was successful ASL INWK+31 ; Set bit 7 of the scooped or destroyed item, to denote SEC ; that it has been killed and should be removed from ROR INWK+31 ; the local bubble .MA61 BNE MA26 ; Jump to MA26 to skip over the collision routines and ; to move on to missile targeting (this BNE is ; effectively a JMP as A will never be zero) .MA67 ; If we get here then we have collided with something, ; but not fatally LDA #1 ; Set the speed in DELTA to 1 (i.e. a sudden stop) STA DELTA LDA #5 ; Set the amount of damage in A to 5 (a small dent) and BNE MA63 ; jump down to MA63 to process the damage (this BNE is ; effectively a JMP as A will never be zero) .MA58 ; If we get here, we have collided with something in a ; potentially fatal way ASL INWK+31 ; Set bit 7 of the ship we just collided with, to SEC ; denote that it has been killed and should be removed ROR INWK+31 ; from the local bubble LDA INWK+35 ; Load A with the energy level of the ship we just hit SEC ; Set the amount of damage in A to 128 + A / 2, so ROR A ; this is quite a big dent, and colliding with higher ; energy ships will cause more damage .MA63 JSR OOPS ; The amount of damage is in A, so call OOPS to reduce ; our shields, and if the shields are gone, there's a ; chance of cargo loss or even death JSR EXNO3 ; Make the sound of colliding with the other ship and ; fall through into MA26 to try targeting a missile ; ****************************************************************************** ; ; Name: Main flight loop (Part 11 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: For each nearby ship: Process missile lock and firing our laser ; Deep dive: Program flow of the main game loop ; Flipping axes between space views ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Continue looping through all the ships in the local bubble, and for each ; one: ; ; * If this is not the front space view, flip the axes of the ship's ; coordinates in INWK ; ; * Process missile lock ; ; * Process our laser firing ; ; ****************************************************************************** .MA26 LDA NEWB ; If bit 7 of the ship's NEWB flags is clear, skip the BPL P%+5 ; following instruction JSR SCAN ; Bit 7 of the ship's NEWB flags is set, which means the ; ship has docked or been scooped, so we draw the ship ; on the scanner, which has the effect of removing it LDA QQ11 ; If this is not a space view, jump to MA15 to skip BNE MA15 ; missile and laser locking JSR PLUT ; Call PLUT to update the geometric axes in INWK to ; match the view (front, rear, left, right) JSR HITCH ; Call HITCH to see if this ship is in the crosshairs, BCC MA8 ; in which case the C flag will be set (so if there is ; no missile or laser lock, we jump to MA8 to skip the ; following) LDA MSAR ; We have missile lock, so check whether the leftmost BEQ MA47 ; missile is currently armed, and if not, jump to MA47 ; to process laser fire, as we can't lock an unarmed ; missile JSR BEEP ; We have missile lock and an armed missile, so call ; the BEEP subroutine to make a short, high beep LDX XSAV ; Call ABORT2 to store the details of this missile LDY #RED2 ; lock, with the targeted ship's slot number in X JSR ABORT2 ; (which we stored in XSAV at the start of this ship's ; loop at MAL1), and set the colour of the missile ; indicator to the colour in Y (red = $0E) .MA47 ; If we get here then the ship is in our sights, but ; we didn't lock a missile, so let's see if we're ; firing the laser LDA LAS ; If we are firing the laser then LAS will contain the BEQ MA8 ; laser power (which we set in MA68 above), so if this ; is zero, jump down to MA8 to skip the following LDX #15 ; We are firing our laser and the ship in INWK is in JSR EXNO ; the crosshairs, so call EXNO to make the sound of ; us making a laser strike on another ship LDA TYPE ; Did we just hit the space station? If so, jump to CMP #SST ; MA14+2 to make the station hostile, skipping the BEQ MA14+2 ; following as we can't destroy a space station CMP #CON ; If the ship we hit is less than #CON - i.e. it's not BCC BURN ; a Constrictor, Cougar, Dodo station or the Elite logo, ; jump to BURN to skip the following LDA LAS ; Set A to the power of the laser we just used to hit ; the ship (i.e. the laser in the current view) CMP #(Armlas AND 127) ; If the laser is not a military laser, jump to MA14+2 BNE MA14+2 ; to skip the following, as only military lasers have ; any effect on the Constrictor or Cougar LSR LAS ; Divide the laser power of the current view by 4, so LSR LAS ; the damage inflicted on the super-ship is a quarter of ; the damage our military lasers would inflict on a ; normal ship .BURN LDA INWK+35 ; Fetch the hit ship's energy from byte #35 and subtract SEC ; our current laser power, and if the result is greater SBC LAS ; than zero, the other ship has survived the hit, so BCS MA14 ; jump down to MA14 to make it angry ASL INWK+31 ; Set bit 7 of the ship byte #31 to indicate that it has SEC ; now been killed ROR INWK+31 LDA TYPE ; Did we just kill an asteroid? If not, jump to nosp, CMP #AST ; otherwise keep going BNE nosp LDA LAS ; Did we kill the asteroid using mining lasers? If not, CMP #Mlas ; jump to nosp, otherwise keep going BNE nosp JSR DORND ; Set A and X to random numbers LDX #SPL ; Set X to the ship type for a splinter AND #3 ; Reduce the random number in A to the range 0-3 JSR SPIN2 ; Call SPIN2 to spawn A items of type X (i.e. spawn ; 0-3 splinters) .nosp LDY #PLT ; Randomly spawn some alloy plates JSR SPIN LDY #OIL ; Randomly spawn some cargo canisters JSR SPIN LDX TYPE ; Set X to the type of the ship that was killed so the ; following call to EXNO2 can award us the correct ; number of fractional kill points JSR EXNO2 ; Call EXNO2 to process the fact that we have killed a ; ship (so increase the kill tally, make an explosion ; sound and so on) .MA14 STA INWK+35 ; Store the hit ship's updated energy in ship byte #35 LDA TYPE ; Call ANGRY to make the target ship or station hostile, JSR ANGRY ; and if this is a ship, wake up its AI and give it a ; kick of speed ; ****************************************************************************** ; ; Name: Main flight loop (Part 12 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: For each nearby ship: Draw the ship, remove if killed, loop back ; Deep dive: Program flow of the main game loop ; Drawing ships ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Continue looping through all the ships in the local bubble, and for each ; one: ; ; * Draw the ship ; ; * Process removal of killed ships ; ; * Loop back up to MAL1 to move onto the next ship in the local bubble ; ; ****************************************************************************** .MA8 JSR LL9 ; Call LL9 to draw the ship we're processing on-screen .MA15 LDY #35 ; Fetch the ship's energy from byte #35 and copy it to LDA INWK+35 ; byte #35 in INF (so the ship's data in K% gets STA (INF),Y ; updated) LDA NEWB ; If bit 7 of the ship's NEWB flags is set, which means BMI KS1S ; the ship has docked or been scooped, jump to KS1S to ; skip the following, as we can't get a bounty for a ; ship that's no longer around LDA INWK+31 ; If bit 7 of the ship's byte #31 is clear, then the BPL MAC1 ; ship hasn't been killed by energy bomb, collision or ; laser fire, so jump to MAC1 to skip the following AND #%00100000 ; If bit 5 of the ship's byte #31 is clear then the BEQ MAC1 ; ship is no longer exploding, so jump to MAC1 to skip ; the following LDA NEWB ; Extract bit 6 of the ship's NEWB flags, so A = 64 if AND #%01000000 ; bit 6 is set, or 0 if it is clear. Bit 6 is set if ; this ship is a cop, so A = 64 if we just killed a ; policeman, otherwise it is 0 ORA FIST ; Update our FIST flag ("fugitive/innocent status") to STA FIST ; at least the value in A, which will instantly make us ; a fugitive if we just shot the sheriff, but won't ; affect our status if the enemy wasn't a copper LDA DLY ; If we already have an in-flight message on-screen (in ORA MJ ; which case DLY > 0), or we are in witchspace (in BNE KS1S ; which case MJ > 0), jump to KS1S to skip showing an ; on-screen bounty for this kill LDY #10 ; Fetch byte #10 of the ship's blueprint, which is the LDA (XX0),Y ; low byte of the bounty awarded when this ship is BEQ KS1S ; killed (in Cr * 10), and if it's zero jump to KS1S as ; there is no on-screen bounty to display TAX ; Put the low byte of the bounty into X INY ; Fetch byte #11 of the ship's blueprint, which is the LDA (XX0),Y ; high byte of the bounty awarded (in Cr * 10), and put TAY ; it into Y JSR MCASH ; Call MCASH to add (Y X) to the cash pot LDA #0 ; Print control code 0 (current cash, right-aligned to JSR MESS ; width 9, then " CR", newline) as an in-flight message .KS1S JMP KS1 ; Process the killing of this ship (which removes this ; ship from its slot and shuffles all the other ships ; down to close up the gap) .MAC1 LDA TYPE ; If the ship we are processing is a planet or sun, BMI MA27 ; jump to MA27 to skip the following two instructions JSR FAROF ; If the ship we are processing is a long way away (its BCC KS1S ; distance in any one direction is > 224, jump to KS1S ; to remove the ship from our local bubble, as it's just ; left the building .MA27 LDY #31 ; Fetch the ship's explosion/killed state from byte #31 LDA INWK+31 ; and copy it to byte #31 in INF (so the ship's data in STA (INF),Y ; K% gets updated) LDX XSAV ; We're done processing this ship, so fetch the ship's ; slot number, which we saved in XSAV back at the start ; of the loop INX ; Increment the slot number to move on to the next slot JMP MAL1 ; And jump back up to the beginning of the loop to get ; the next ship in the local bubble for processing ; ****************************************************************************** ; ; Name: Main flight loop (Part 13 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: Show energy bomb effect, charge shields and energy banks ; Deep dive: Program flow of the main game loop ; Scheduling tasks with the main loop counter ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Show energy bomb effect (if applicable) ; ; * Charge shields and energy banks (every 7 iterations of the main loop) ; ; ****************************************************************************** .MA18 LDA BOMB ; If we set off our energy bomb (see MA24 above), then BPL MA77 ; BOMB is now negative, so this skips to MA21 if our ; energy bomb is not going off ASL BOMB ; We set off our energy bomb, so rotate BOMB to the ; left by one place. BOMB was rotated left once already ; during this iteration of the main loop, back at MA24, ; so if this is the first pass it will already be ; %11111110, and this will shift it to %11111100 - so ; if we set off an energy bomb, it stays activated ; (BOMB > 0) for four iterations of the main loop BMI MA77 ; If the result has bit 7 set, skip the following ; instruction as the bomb is still going off JSR BOMBOFF ; Our energy bomb has finished going off, so call ; BOMBOFF to turn off the bomb effect .MA77 LDA MCNT ; Fetch the main loop counter and calculate MCNT mod 8, AND #7 ; jumping to MA22 if it is non-zero (so the following BNE MA22 ; code only runs every 8 iterations of the main loop) LDX ENERGY ; Fetch our ship's energy levels and skip to b if bit 7 BPL b ; is not set, i.e. only charge the shields from the ; energy banks if they are at more than 50% charge LDX ASH ; Call SHD to recharge our aft shield and update the JSR SHD ; shield status in ASH STX ASH LDX FSH ; Call SHD to recharge our forward shield and update JSR SHD ; the shield status in FSH STX FSH .b SEC ; Set A = ENERGY + ENGY + 1, so our ship's energy LDA ENGY ; level goes up by 2 if we have an energy unit fitted, ADC ENERGY ; otherwise it goes up by 1 BCS P%+5 ; If the value of A did not overflow (the maximum STA ENERGY ; energy level is $FF), then store A in ENERGY ; ****************************************************************************** ; ; Name: Main flight loop (Part 14 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: Spawn a space station if we are close enough to the planet ; Deep dive: Program flow of the main game loop ; Scheduling tasks with the main loop counter ; Ship data blocks ; The space station safe zone ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Spawn a space station if we are close enough to the planet (every 32 ; iterations of the main loop) ; ; ****************************************************************************** LDA MJ ; If we are in witchspace, jump down to MA23S to skip BNE MA23S ; the following, as there are no space stations in ; witchspace LDA MCNT ; Fetch the main loop counter and calculate MCNT mod 32, AND #31 ; jumping to MA93 if it is on-zero (so the following BNE MA93 ; code only runs every 32 iterations of the main loop) LDA SSPR ; If we are inside the space station safe zone, jump to BNE MA23S ; MA23S to skip the following, as we already have a ; space station and don't need another TAY ; Set Y = A = 0 (A is 0 as we didn't branch with the ; previous BNE instruction) JSR MAS2 ; Call MAS2 to calculate the largest distance to the BNE MA23S ; planet in any of the three axes, and if it's ; non-zero, jump to MA23S to skip the following, as we ; are too far from the planet to bump into a space ; station ; We now want to spawn a space station, so first we ; need to set up a ship data block for the station in ; INWK that we can then pass to NWSPS to add a new ; station to our bubble of universe. We do this by ; copying the planet data block from K% to INWK so we ; can work on it, but we only need the first 29 bytes, ; as we don't need to worry about bytes #29 to #35 ; for planets (as they don't have rotation counters, ; AI, explosions, missiles, a ship line heap or energy ; levels) LDX #28 ; So we set a counter in X to copy 29 bytes from K%+0 ; to K%+28 .MAL4 LDA K%,X ; Load the X-th byte of K% and store in the X-th byte STA INWK,X ; of the INWK workspace DEX ; Decrement the loop counter BPL MAL4 ; Loop back for the next byte until we have copied the ; first 28 bytes of K% to INWK ; We now check the distance from our ship (at the ; origin) towards the point where we will spawn the ; space station if we are close enough ; ; This point is calculated by starting at the planet's ; centre and adding 2 * nosev, which takes us to a point ; above the planet's surface, at an altitude that ; matches the planet's radius ; ; This point pitches and rolls around the planet as the ; nosev vector rotates with the planet, and if our ship ; is within a distance of (192 0) from this point in all ; three axes, then we spawn the space station at this ; point, with the station's slot facing towards the ; planet, along the nosev vector ; ; This works because in the following, we calculate the ; station's coordinates one axis at a time, and store ; the results in the INWK block, so by the time we have ; calculated and checked all three, the ship data block ; is set up with the correct spawning coordinates INX ; Set X = 0 (as we ended the above loop with X as $FF) LDY #9 ; Call MAS1 with X = 0, Y = 9 to do the following: JSR MAS1 ; ; (x_sign x_hi x_lo) += (nosev_x_hi nosev_x_lo) * 2 ; ; A = |x_sign| BNE MA23S ; If A > 0, jump to MA23S to skip the following, as we ; are too far from the planet in the x-direction to ; bump into a space station LDX #3 ; Call MAS1 with X = 3, Y = 11 to do the following: LDY #11 ; JSR MAS1 ; (y_sign y_hi y_lo) += (nosev_y_hi nosev_y_lo) * 2 ; ; A = |y_sign| BNE MA23S ; If A > 0, jump to MA23S to skip the following, as we ; are too far from the planet in the y-direction to ; bump into a space station LDX #6 ; Call MAS1 with X = 6, Y = 13 to do the following: LDY #13 ; JSR MAS1 ; (z_sign z_hi z_lo) += (nosev_z_hi nosev_z_lo) * 2 ; ; A = |z_sign| BNE MA23S ; If A > 0, jump to MA23S to skip the following, as we ; are too far from the planet in the z-direction to ; bump into a space station LDA #192 ; Call FAROF2 to compare x_hi, y_hi and z_hi with 192, JSR FAROF2 ; which will set the C flag if all three are < 192, or ; clear the C flag if any of them are >= 192 BCC MA23S ; Jump to MA23S if any one of x_hi, y_hi or z_hi are ; >= 192 (i.e. they must all be < 192 for us to be near ; enough to the planet to bump into a space station) JSR WPLS ; Call WPLS to remove the sun from the screen, as we ; can't have both the sun and the space station at the ; same time JSR NWSPS ; Add a new space station to our local bubble of ; universe .MA23S JMP MA23 ; Jump to MA23 to skip the following planet and sun ; altitude checks ; ****************************************************************************** ; ; Name: Main flight loop (Part 15 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: Perform altitude checks with the planet and sun and process fuel ; scooping if appropriate ; Deep dive: Program flow of the main game loop ; Scheduling tasks with the main loop counter ; The Trumbles mission ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Perform an altitude check with the planet (every 32 iterations of the main ; loop, on iteration 10 of each 32) ; ; * Perform an altitude check with the sun and process fuel scooping (every ; 32 iterations of the main loop, on iteration 20 of each 32) ; ; ****************************************************************************** .MA22 LDA MJ ; If we are in witchspace, jump down to MA23S to skip BNE MA23S ; the following, as there are no planets or suns to ; bump into in witchspace LDA MCNT ; Fetch the main loop counter and calculate MCNT mod 32, AND #31 ; which tells us the position of this loop in each block ; of 32 iterations .MA93 CMP #10 ; If this is the tenth iteration in this block of 32, BNE MA29 ; do the following, otherwise jump to MA29 to skip the ; planet altitude check and move on to the sun distance ; check LDA #50 ; If our energy bank status in ENERGY is >= 50, skip CMP ENERGY ; printing the following message (so the message is BCC P%+6 ; only shown if our energy is low) ASL A ; Print recursive token 100 ("ENERGY LOW{beep}") as an JSR MESS ; in-flight message LDY #$FF ; Set our altitude in ALTIT to $FF, the maximum STY ALTIT INY ; Set Y = 0 JSR m ; Call m to calculate the maximum distance to the ; planet in any of the three axes, returned in A BNE MA23 ; If A > 0 then we are a fair distance away from the ; planet in at least one axis, so jump to MA23 to skip ; the rest of the altitude check JSR MAS3 ; Set A = x_hi^2 + y_hi^2 + z_hi^2, so using Pythagoras ; we now know that A now contains the square of the ; distance between our ship (at the origin) and the ; centre of the planet at (x_hi, y_hi, z_hi) BCS MA23 ; If the C flag was set by MAS3, then the result ; overflowed (was greater than $FF) and we are still a ; fair distance from the planet, so jump to MA23 as we ; haven't crashed into the planet SBC #36 ; Subtract 37 from x_hi^2 + y_hi^2 + z_hi^2 ; ; The SBC subtracts 37 as we just passed through a BCS ; so we know the C flag is clear ; ; When we do the 3D Pythagoras calculation, we only use ; the high bytes of the coordinates, so that's x_hi, ; y_hi and z_hi and ; ; The planet radius is (0 96 0), as defined in the ; PLANET routine, so the high byte is 96 ; ; When we square the coordinates above and add them, ; the result gets divided by 256 (otherwise the result ; wouldn't fit into one byte), so if we do the same for ; the planet's radius, we get: ; ; 96 * 96 / 256 = 36 ; ; So for the planet, the equivalent figure to test the ; sum of the _hi bytes against is 36, so A now contains ; the high byte of our altitude above the planet ; surface, squared, with an extra 1 subtracted so the ; test in the next instruction will ensure we crash ; even if we are exactly one planet radius away BCC MA28 ; If A < 0 then jump to MA28 as we have crashed into ; the planet STA R ; We are getting close to the planet, so we need to JSR LL5 ; work out how close. We know from the above that A ; contains our altitude squared, so we store A in R ; and call LL5 to calculate: ; ; Q = SQRT(R Q) = SQRT(A Q) ; ; Interestingly, Q doesn't appear to be set to 0 for ; this calculation, so presumably this doesn't make a ; difference LDA Q ; Store the result in ALTIT, our altitude STA ALTIT BNE MA23 ; If our altitude is non-zero then we haven't crashed, ; so jump to MA23 to skip to the next section .MA28 JMP DEATH ; If we get here then we just crashed into the planet ; or got too close to the sun, so jump to DEATH to start ; the funeral preparations and return from the main ; flight loop using a tail call .MA29 CMP #15 ; If this is the 15th iteration in this block of 32, BNE MA33 ; do the following, otherwise jump to MA33 to skip the ; docking computer manoeuvring LDA auto ; If auto is zero, then the docking computer is not BEQ MA23 ; activated, so jump to MA23 to skip to the next ; section LDA #123 ; Set A = 123 and jump down to MA34 to print token 123 BNE MA34 ; ("DOCKING COMPUTERS ON") as an in-flight message .MA33 CMP #20 ; If this is the 20th iteration in this block of 32, BNE MA23 ; do the following, otherwise jump to MA23 to skip the ; sun altitude check LDA #30 ; Set CABTMP to 30, the cabin temperature in deep space STA CABTMP ; (i.e. one notch on the dashboard bar) LDA SSPR ; If we are inside the space station safe zone, jump to BNE MA23 ; MA23 to skip the following, as we can't have both the ; sun and space station at the same time, so we clearly ; can't be flying near the sun LDY #NI% ; Set Y to NI%, which is the offset in K% for the sun's ; data block, as the second block at K% is reserved for ; the sun (or space station) JSR MAS2 ; Call MAS2 to calculate the largest distance to the BNE MA23 ; sun in any of the three axes, and if it's non-zero, ; jump to MA23 to skip the following, as we are too far ; from the sun for scooping or temperature changes JSR MAS3 ; Set (A ?) = x_hi^2 + y_hi^2 + z_hi^2, so using ; Pythagoras we now know that A now contains the high ; byte of the square of the distance between our ship ; (at the origin) and the heart of the sun at coordinate ; (x_hi, y_hi, z_hi) ; ; If the calculation overflows so it doesn't fit into ; one byte, then A is set to $FF and the C flag is set EOR #%11111111 ; Invert A, so A is now small if we are far from the ; sun and large if we are close to the sun, in the ; range 0 = far away to $FF = extremely close, ouch, ; hot, hot, hot! ADC #30 ; Add the minimum cabin temperature of 30, plus the C ; flag, so we get one of the following: ; ; * If the MAS3 calculation overflowed then we are a ; long way from the sun, A will be zero and the C ; flag will be set, so this addition sets A = 31 ; and clears the C flag ; ; * If the result of the MAS3 calculation fitted into ; one byte, then A will be in the range 0 to 255 and ; the C flag will be clear, so this addition has a ; result in the range 0 to 285, with the higher ; values overflowing the addition and setting the ; C flag ; ; So the C flag is set if the cabin temperature is too ; hot to handle, and if it's clear then A contains the ; cabin temperature STA CABTMP ; Store the updated cabin temperature BCS MA28 ; If the C flag is set then jump to MA28 to die, as ; our temperature is off the scale CMP #224 ; If the cabin temperature < 224 then jump to MA23 to BCC MA23 ; skip fuel scooping, as we aren't close enough CMP #240 ; If the cabin temperature < 240 then jump to nokilltr BCC nokilltr ; as the heat isn't high enough to kill Trumbles LDA #%101 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 1 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on ; ; See the memory map at the top of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore LDA VIC+$15 ; Clear bits 2-7 of VIC register $15 to disable sprites AND #%00000011 ; 2 to 7, so this hides the Trumble sprites STA VIC+$15 LDA #%100 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore LSR TRIBBLE+1 ; Halve the number of Trumbles in TRIBBLE(1 0) as the ROR TRIBBLE ; cabin temperature is high enough to kill them off ; (this will eventually bring the number down to zero) .nokilltr LDA BST ; If we don't have fuel scoops fitted, jump to BA23 to BEQ MA23 ; skip fuel scooping, as we can't scoop without fuel ; scoops LDA DELT4+1 ; We are now successfully fuel scooping, so it's time LSR A ; to work out how much fuel we're scooping. Fetch the ; high byte of DELT4, which contains our current speed ; divided by 4, and halve it to get our current speed ; divided by 8 (so it's now a value between 1 and 5, as ; our speed is normally between 1 and 40). This gives ; us the amount of fuel that's being scooped in A, so ; the faster we go, the more fuel we scoop, and because ; the fuel levels are stored as 10 * the fuel in light ; years, that means we just scooped between 0.1 and 0.5 ; light years of free fuel ADC QQ14 ; Set A = A + the current fuel level * 10 (from QQ14) CMP #70 ; If A > 70 then set A = 70 (as 70 is the maximum fuel BCC P%+4 ; level, or 7.0 light years) LDA #70 STA QQ14 ; Store the updated fuel level in QQ14 LDA #160 ; Set A to token 160 ("FUEL SCOOPS ON") .MA34 JSR MESS ; Print the token in A as an in-flight message ; ****************************************************************************** ; ; Name: Main flight loop (Part 16 of 16) ; Type: Subroutine ; Category: Main loop ; Summary: Process laser pulsing, E.C.M. energy drain, call stardust routine ; Deep dive: Program flow of the main game loop ; ; ------------------------------------------------------------------------------ ; ; The main flight loop covers most of the flight-specific aspects of Elite. This ; section covers the following: ; ; * Process laser pulsing ; ; * Process E.C.M. energy drain ; ; * Jump to the stardust routine if we are in a space view ; ; * Return from the main flight loop ; ; ****************************************************************************** .MA23 LDA LAS2 ; If the current view has no laser, jump to MA16 to skip BEQ MA16 ; the following LDA LASCT ; If LASCT >= 8, jump to MA16 to skip the following, so CMP #8 ; for a pulse laser with a LASCT between 8 and 10, the BCS MA16 ; laser stays on, but for a LASCT of 7 or less it gets ; turned off and stays off until LASCT reaches zero and ; the next pulse can start (if the fire button is still ; being pressed) ; ; For pulse lasers, LASCT gets set to 10 in ma1 above, ; and it decrements every vertical sync (50 times a ; second), so this means it pulses five times a second, ; with the laser being on for the first 3/10 of each ; pulse and off for the rest of the pulse ; ; If this is a beam laser, LASCT is 0 so we always keep ; going here. This means the laser doesn't pulse, but it ; does get drawn and removed every cycle, in a slightly ; different place each time, so the beams still flicker ; around the screen JSR LASLI2 ; Redraw the existing laser lines, which has the effect ; of removing them from the screen LDA #0 ; Set LAS2 to 0 so if this is a pulse laser, it will STA LAS2 ; skip over the above until the next pulse (this has no ; effect if this is a beam laser) .MA16 LDA ECMP ; If our E.C.M is not on, skip to MA69, otherwise keep BEQ MA69 ; going to drain some energy JSR DENGY ; Call DENGY to deplete our energy banks by 1 BEQ MA70 ; If we have no energy left, jump to MA70 to turn our ; E.C.M. off .MA69 LDA ECMA ; If an E.C.M is going off (ours or an opponent's) then BEQ MA66 ; keep going, otherwise skip to MA66 DEC ECMA ; Decrement the E.C.M. countdown timer, and if it has BNE MA66 ; reached zero, keep going, otherwise skip to MA66 .MA70 JSR ECMOF ; If we get here then either we have either run out of ; energy, or the E.C.M. timer has run down, so switch ; off the E.C.M. .MA66 LDA QQ11 ; If this is not a space view (i.e. QQ11 is non-zero) BNE oh ; then jump to oh to return from the main flight loop ; (as oh is an RTS) JMP STARS ; This is a space view, so jump to the STARS routine to ; process the stardust, and return from the main flight ; loop using a tail call ;JMP PBFL ; This instruction is commented out in the original ; source ; ****************************************************************************** ; ; Name: SPIN ; Type: Subroutine ; Category: Universe ; Summary: Randomly spawn cargo from a destroyed ship ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The type of cargo to consider spawning (typically #PLT ; or #OIL) ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; oh Contains an RTS ; ; SPIN2 Remove any randomness: spawn cargo of a specific type ; (given in X), and always spawn the number given in A ; ; ****************************************************************************** .SPIN JSR DORND ; Fetch a random number, and jump to oh if it is BPL oh ; positive (50% chance) TYA ; Copy the cargo type from Y into A and X TAX LDY #0 ; Fetch the first byte of the hit ship's blueprint, AND (XX0),Y ; which determines the maximum number of bits of ; debris shown when the ship is destroyed, and AND ; with the random number we just fetched AND #15 ; Reduce the random number in A to the range 0-15 .SPIN2 STA CNT ; Store the result in CNT, so CNT contains a random ; number between 0 and the maximum number of bits of ; debris that this ship will release when destroyed ; (to a maximum of 15 bits of debris) .spl BEQ oh ; We're going to go round a loop using CNT as a counter ; so this checks whether the counter is zero and jumps ; to oh when it gets there (which might be straight ; away) LDA #0 ; Call SFS1 to spawn the specified cargo from the now JSR SFS1 ; deceased parent ship, giving the spawned canister an ; AI flag of 0 (no AI, zero aggression, no E.C.M.) DEC CNT ; Decrease the loop counter BNE spl+2 ; Jump back up to the LDA $0 instruction above (this BPL ; is effectively a JMP as CNT will never be negative) .oh RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: BOMBOFF ; Type: Subroutine ; Category: Drawing the screen ; Summary: Switch off the energy bomb effect ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A is set to 0 ; ; ****************************************************************************** .BOMBOFF LDA #%11000000 ; Clear bit 4 of moonflower so the screen returns to STA moonflower ; standard bitmap mode, so the space view goes back to ; its normal monochrome, small-pixel appearance LDA #0 ; Set welcome to 0 to stop the background colour from STA welcome ; flashing different colours (as 0 represents black, and ; the energy bomb flashes between black and the colour ; in welcome) RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MT27 ; Type: Subroutine ; Category: Text ; Summary: Print the captain's name during mission briefings ; Deep dive: Extended text tokens ; The Constrictor mission ; ; ------------------------------------------------------------------------------ ; ; This routine prints the following tokens, depending on the galaxy number: ; ; * Token 217 ("CURRUTHERS") in galaxy 0 ; ; * Token 218 ("FOSDYKE SMYTHE") in galaxy 1 ; ; * Token 219 ("FORTESQUE") in galaxy 2 ; ; This is used when printing extended token 213 as part of the mission ; briefings, which looks like this when printed: ; ; Commander {commander name}, I am Captain {mission captain's name} of Her ; Majesty's Space Navy ; ; where {mission captain's name} is replaced by one of the names above. ; ; ****************************************************************************** .MT27 LDA #217 ; Set A = 217, so when we fall through into MT28, the ; 217 gets added to the current galaxy number, so the ; extended token that is printed is 217-219 (as this is ; only called in galaxies 0 through 2) BNE P%+4 ; Skip the next instruction ; ****************************************************************************** ; ; Name: MT28 ; Type: Subroutine ; Category: Text ; Summary: Print the location hint during the mission 1 briefing ; Deep dive: Extended text tokens ; The Constrictor mission ; ; ------------------------------------------------------------------------------ ; ; This routine prints the following tokens, depending on the galaxy number: ; ; * Token 220 ("WAS LAST SEEN AT {single cap}REESDICE") in galaxy 0 ; ; * Token 221 ("IS BELIEVED TO HAVE JUMPED TO THIS GALAXY") in galaxy 1 ; ; This is used when printing extended token 10 as part of the mission 1 ; briefing, which looks like this when printed: ; ; It went missing from our ship yard on Xeer five months ago and {mission 1 ; location hint} ; ; where {mission 1 location hint} is replaced by one of the names above. ; ; ****************************************************************************** .MT28 LDA #220 ; Set A = galaxy number in GCNT + 220, which is in the CLC ; range 220-221, as this is only called in galaxies 0 ADC GCNT ; and 1 BNE DETOK ; Jump to DETOK to print extended token 220-221, ; returning from the subroutine using a tail call (this ; BNE is effectively a JMP as A is never zero) ; ****************************************************************************** ; ; Name: DETOK3 ; Type: Subroutine ; Category: Text ; Summary: Print an extended recursive token from the RUTOK token table ; Deep dive: Extended system descriptions ; Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The recursive token to be printed, in the range 0-255 ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A is preserved ; ; Y Y is preserved ; ; V(1 0) V(1 0) is preserved ; ; ****************************************************************************** .DETOK3 PHA ; Store A on the stack, so we can retrieve it later TAX ; Copy the token number from A into X TYA ; Store Y on the stack PHA LDA V ; Store V(1 0) on the stack PHA LDA V+1 PHA LDA #LO(RUTOK) ; Set V to the low byte of RUTOK STA V LDA #HI(RUTOK) ; Set A to the high byte of RUTOK BNE DTEN ; Call DTEN to print token number X from the RUTOK ; table and restore the values of A, Y and V(1 0) from ; the stack, returning from the subroutine using a tail ; call (this BNE is effectively a JMP as A is never ; zero) ; ****************************************************************************** ; ; Name: DETOK ; Type: Subroutine ; Category: Text ; Summary: Print an extended recursive token from the TKN1 token table ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The recursive token to be printed, in the range 1-255 ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A is preserved ; ; Y Y is preserved ; ; V(1 0) V(1 0) is preserved ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; DTEN Print recursive token number X from the token table ; pointed to by (A V), used to print tokens from the RUTOK ; table via calls to DETOK3 ; ; ****************************************************************************** .DETOK PHA ; Store A on the stack, so we can retrieve it later TAX ; Copy the token number from A into X TYA ; Store Y on the stack PHA LDA V ; Store V(1 0) on the stack PHA LDA V+1 PHA LDA #LO(TKN1) ; Set V to the low byte of TKN1 STA V LDA #HI(TKN1) ; Set A to the high byte of TKN1, so when we fall ; through into DTEN, V(1 0) gets set to the address of ; the TKN1 token table .DTEN STA V+1 ; Set the high byte of V(1 0) to A, so V(1 0) now points ; to the start of the token table to use LDY #0 ; First, we need to work our way through the table until ; we get to the token that we want to print. Tokens are ; delimited by #VE, and VE EOR VE = 0, so we work our ; way through the table in, counting #VE delimiters ; until we have passed X of them, at which point we jump ; down to DTL2 to do the actual printing. So first, we ; set a counter Y to point to the character offset as we ; scan through the table .DTL1 LDA (V),Y ; Load the character at offset Y in the token table, ; which is the next character from the token table EOR #VE ; Tokens are stored in memory having been EOR'd with ; #VE, so we repeat the EOR to get the actual character ; in this token BNE DT1 ; If the result is non-zero, then this is a character ; in a token rather than the delimiter (which is #VE), ; so jump to DT1 DEX ; We have just scanned the end of a token, so decrement ; X, which contains the token number we are looking for BEQ DTL2 ; If X has now reached zero then we have found the token ; we are looking for, so jump down to DTL2 to print it .DT1 INY ; Otherwise this isn't the token we are looking for, so ; increment the character pointer BNE DTL1 ; If Y hasn't just wrapped around to 0, loop back to ; DTL1 to process the next character INC V+1 ; We have just crossed into a new page, so increment ; V+1 so that V points to the start of the new page BNE DTL1 ; Jump back to DTL1 to process the next character (this ; BNE is effectively a JMP as V+1 won't reach zero ; before we reach the end of the token table) .DTL2 INY ; We just detected the delimiter byte before the token ; that we want to print, so increment the character ; pointer to point to the first character of the token, ; rather than the delimiter BNE P%+4 ; If Y hasn't just wrapped around to 0, skip the next ; instruction INC V+1 ; We have just crossed into a new page, so increment ; V+1 so that V points to the start of the new page LDA (V),Y ; Load the character at offset Y in the token table, ; which is the next character from the token we want to ; print EOR #VE ; Tokens are stored in memory having been EOR'd with ; #VE, so we repeat the EOR to get the actual character ; in this token BEQ DTEX ; If the result is zero, then this is the delimiter at ; the end of the token to print (which is #VE), so jump ; to DTEX to return from the subroutine, as we are done ; printing JSR DETOK2 ; Otherwise call DETOK2 to print this part of the token JMP DTL2 ; Jump back to DTL2 to process the next character .DTEX PLA ; Restore V(1 0) from the stack, so it is preserved STA V+1 ; through calls to this routine PLA STA V PLA ; Restore Y from the stack, so it is preserved through TAY ; calls to this routine PLA ; Restore A from the stack, so it is preserved through ; calls to this routine RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DETOK2 ; Type: Subroutine ; Category: Text ; Summary: Print an extended text token (1-255) ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The token to be printed (1-255) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A is preserved ; ; Y Y is preserved ; ; V(1 0) V(1 0) is preserved ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; DTS Print a single letter in the correct case ; ; ****************************************************************************** .DETOK2 CMP #32 ; If A < 32 then this is a jump token, so skip to DT3 to BCC DT3 ; process it BIT DTW3 ; If bit 7 of DTW3 is clear, then extended tokens are BPL DT8 ; enabled, so jump to DT8 to process them ; If we get there then this is not a jump token and ; extended tokens are not enabled, so we can call the ; standard text token routine at TT27 to print the token TAX ; Copy the token number from A into X TYA ; Store Y on the stack PHA LDA V ; Store V(1 0) on the stack PHA LDA V+1 PHA TXA ; Copy the token number from X back into A JSR TT27 ; Call TT27 to print the text token JMP DT7 ; Jump to DT7 to restore V(1 0) and Y from the stack and ; return from the subroutine .DT8 ; If we get here then this is not a jump token and ; extended tokens are enabled CMP #'[' ; If A < ASCII "[" (i.e. A <= ASCII "Z", or 90) then BCC DTS ; this is a printable ASCII character, so jump down to ; DTS to print it CMP #129 ; If A < 129, so A is in the range 91-128, jump down to BCC DT6 ; DT6 to print a randomised token from the MTIN table CMP #215 ; If A < 215, so A is in the range 129-214, jump to BCC DETOK ; DETOK as this is a recursive token, returning from the ; subroutine using a tail call ; If we get here then A >= 215, so this is a two-letter ; token from the extended TKN2/QQ16 table SBC #215 ; Subtract 215 to get a token number in the range 0-12 ; (the C flag is set as we passed through the BCC above, ; so this subtraction is correct) ASL A ; Set A = A * 2, so it can be used as a pointer into the ; two-letter token tables at TKN2 and QQ16 PHA ; Store A on the stack, so we can restore it for the ; second letter below TAX ; Fetch the first letter of the two-letter token from LDA TKN2,X ; TKN2, which is at TKN2 + X JSR DTS ; Call DTS to print it PLA ; Restore A from the stack and transfer it into X TAX LDA TKN2+1,X ; Fetch the second letter of the two-letter token from ; TKN2, which is at TKN2 + X + 1, and fall through into ; DTS to print it .DTS CMP #'A' ; If A < ASCII "A", jump to DT9 to print this as ASCII BCC DT9 BIT DTW6 ; If bit 7 of DTW6 is set, then lower case has been BMI DT10 ; enabled by jump token 13, {lower case}, so jump to ; DT10 to apply the lower case and single cap masks BIT DTW2 ; If bit 7 of DTW2 is set, then we are not currently BMI DT5 ; printing a word, so jump to DT5 so we skip the setting ; of lower case in Sentence Case (which we only want to ; do when we are already printing a word) .DT10 ORA DTW1 ; Convert the character to lower case if DTW1 is ; %00100000 (i.e. if we are in {sentence case} mode) .DT5 AND DTW8 ; Convert the character to upper case if DTW8 is ; %11011111 (i.e. after a {single cap} token) .DT9 JMP DASC ; Jump to DASC to print the ASCII character in A, ; returning from the routine using a tail call .DT3 ; If we get here then the token number in A is in the ; range 1 to 32, so this is a jump token that should ; call the corresponding address in the jump table at ; JMTB TAX ; Copy the token number from A into X TYA ; Store Y on the stack PHA LDA V ; Store V(1 0) on the stack PHA LDA V+1 PHA TXA ; Copy the token number from X back into A ASL A ; Set A = A * 2, so it can be used as a pointer into the ; jump table at JMTB, though because the original range ; of values is 1-32, so the doubled range is 2-64, we ; need to take the offset into the jump table from ; JMTB-2 rather than JMTB TAX ; Copy the doubled token number from A into X LDA JMTB-2,X ; Set DTM(2 1) to the X-th address from the table at STA DTM+1 ; JTM-2, which modifies the JSR DASC instruction at LDA JMTB-1,X ; label DTM below so that it calls the subroutine at the STA DTM+2 ; relevant address from the JMTB table TXA ; Copy the doubled token number from X back into A LSR A ; Halve A to get the original token number .DTM JSR DASC ; Call the relevant JMTB subroutine, as this instruction ; will have been modified by the above to point to the ; relevant address .DT7 PLA ; Restore V(1 0) from the stack, so it is preserved STA V+1 ; through calls to this routine PLA STA V PLA ; Restore Y from the stack, so it is preserved through TAY ; calls to this routine RTS ; Return from the subroutine .DT6 ; If we get here then the token number in A is in the ; range 91-128, which means we print a randomly picked ; token from the token range given in the corresponding ; entry in the MTIN table STA SC ; Store the token number in SC TYA ; Store Y on the stack PHA LDA V ; Store V(1 0) on the stack PHA LDA V+1 PHA JSR DORND ; Set X to a random number TAX LDA #0 ; Set A to 0, so we can build a random number from 0 to ; 4 in A plus the C flag, with each number being equally ; likely CPX #51 ; Add 1 to A if X >= 51 ADC #0 CPX #102 ; Add 1 to A if X >= 102 ADC #0 CPX #153 ; Add 1 to A if X >= 153 ADC #0 CPX #204 ; Set the C flag if X >= 204 LDX SC ; Fetch the token number from SC into X, so X is now in ; the range 91-128 ADC MTIN-91,X ; Set A = MTIN-91 + token number (91-128) + random (0-4) ; = MTIN + token number (0-37) + random (0-4) JSR DETOK ; Call DETOK to print the extended recursive token in A JMP DT7 ; Jump to DT7 to restore V(1 0) and Y from the stack and ; return from the subroutine using a tail call ; ****************************************************************************** ; ; Name: MT1 ; Type: Subroutine ; Category: Text ; Summary: Switch to ALL CAPS when printing extended tokens ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This routine sets the following: ; ; * DTW1 = %00000000 (do not change case to lower case) ; ; * DTW6 = %00000000 (lower case is not enabled) ; ; ****************************************************************************** .MT1 LDA #%00000000 ; Set A = %00000000, so when we fall through into MT2, ; both DTW1 and DTW6 get set to %00000000 EQUB $2C ; Skip the next instruction by turning it into ; $2C $A9 $20, or BIT $20A9, which does nothing apart ; from affect the flags ; ****************************************************************************** ; ; Name: MT2 ; Type: Subroutine ; Category: Text ; Summary: Switch to Sentence Case when printing extended tokens ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This routine sets the following: ; ; * DTW1 = %00100000 (apply lower case to the second letter of a word onwards) ; ; * DTW6 = %00000000 (lower case is not enabled) ; ; ****************************************************************************** .MT2 LDA #%00100000 ; Set DTW1 = %00100000 STA DTW1 LDA #00000000 ; Set DTW6 = %00000000 STA DTW6 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MT8 ; Type: Subroutine ; Category: Text ; Summary: Tab to column 6 and start a new word when printing extended tokens ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This routine sets the following: ; ; * XC = 6 (tab to column 6) ; ; * DTW2 = %11111111 (we are not currently printing a word) ; ; ****************************************************************************** .MT8 LDA #6 ; Move the text cursor to column 6 JSR DOXC LDA #%11111111 ; Set all the bits in DTW2 STA DTW2 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MT9 ; Type: Subroutine ; Category: Text ; Summary: Clear the screen and set the current view type to 1 ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This routine sets the following: ; ; * XC = 1 (tab to column 1) ; ; before calling TT66 to clear the screen and set the view type to 1. ; ; ****************************************************************************** .MT9 LDA #1 ; Call DOXC to move the text cursor to column 1 JSR DOXC JMP TT66 ; Jump to TT66 to clear the screen and set the current ; view type to 1, returning from the subroutine using a ; tail call ; ****************************************************************************** ; ; Name: MT13 ; Type: Subroutine ; Category: Text ; Summary: Switch to lower case when printing extended tokens ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This routine sets the following: ; ; * DTW1 = %00100000 (apply lower case to the second letter of a word onwards) ; ; * DTW6 = %10000000 (lower case is enabled) ; ; ****************************************************************************** .MT13 LDA #%10000000 ; Set DTW6 = %10000000 STA DTW6 LDA #%00100000 ; Set DTW1 = %00100000 STA DTW1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MT6 ; Type: Subroutine ; Category: Text ; Summary: Switch to standard tokens in Sentence Case ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This routine sets the following: ; ; * QQ17 = %10000000 (set Sentence Case for standard tokens) ; ; * DTW3 = %11111111 (print standard tokens) ; ; ****************************************************************************** .MT6 LDA #%10000000 ; Set bit 7 of QQ17 to switch standard tokens to STA QQ17 ; Sentence Case LDA #%11111111 ; Set A = %11111111, so when we fall through into MT5, ; DTW3 gets set to %11111111 and calls to DETOK print ; standard tokens EQUB $2C ; Skip the next instruction by turning it into ; $2C $A9 $00, or BIT $00A9, which does nothing apart ; from affect the flags ; ****************************************************************************** ; ; Name: MT5 ; Type: Subroutine ; Category: Text ; Summary: Switch to extended tokens ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This routine sets the following: ; ; * DTW3 = %00000000 (print extended tokens) ; ; ****************************************************************************** .MT5 LDA #%00000000 ; Set DTW3 = %00000000, so that calls to DETOK print STA DTW3 ; extended tokens RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MT14 ; Type: Subroutine ; Category: Text ; Summary: Switch to justified text when printing extended tokens ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This routine sets the following: ; ; * DTW4 = %10000000 (justify text, print buffer on carriage return) ; ; * DTW5 = 0 (reset line buffer size) ; ; ****************************************************************************** .MT14 LDA #%10000000 ; Set A = %10000000, so when we fall through into MT15, ; DTW4 gets set to %10000000 EQUB $2C ; Skip the next instruction by turning it into ; $2C $A9 $00, or BIT $00A9, which does nothing apart ; from affect the flags ; ****************************************************************************** ; ; Name: MT15 ; Type: Subroutine ; Category: Text ; Summary: Switch to left-aligned text when printing extended tokens ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This routine sets the following: ; ; * DTW4 = %00000000 (do not justify text, print buffer on carriage return) ; ; * DTW5 = 0 (reset line buffer size) ; ; ****************************************************************************** .MT15 LDA #0 ; Set DTW4 = %00000000 STA DTW4 ASL A ; Set DTW5 = 0 (even when we fall through from MT14 with STA DTW5 ; A set to %10000000) RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MT17 ; Type: Subroutine ; Category: Text ; Summary: Print the selected system's adjective, e.g. Lavian for Lave ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; The adjective for the current system is generated by taking the system name, ; removing the last character if it is a vowel, and adding "-ian" to the end, ; so: ; ; * Lave gives Lavian (as in "Lavian tree grub") ; ; * Leesti gives Leestian (as in "Leestian Evil Juice") ; ; This routine is called by jump token 17, {system name adjective}, and it can ; only be used when justified text is being printed - i.e. following jump token ; 14, {justify} - because the routine needs to use the line buffer to work. ; ; ****************************************************************************** .MT17 LDA QQ17 ; Set QQ17 = %10111111 to switch to Sentence Case AND #%10111111 STA QQ17 LDA #3 ; Print control code 3 (selected system name) into the JSR TT27 ; line buffer LDX DTW5 ; Load the last character of the line buffer BUF into A LDA BUF-1,X ; (as DTW5 contains the buffer size, so character DTW5-1 ; is the last character in the buffer BUF) JSR VOWEL ; Test whether the character is a vowel, in which case ; this will set the C flag BCC MT171 ; If the character is not a vowel, skip the following ; instruction DEC DTW5 ; The character is a vowel, so decrement DTW5, which ; removes the last character from the line buffer (i.e. ; it removes the trailing vowel from the system name) .MT171 LDA #153 ; Print extended token 153 ("IAN"), returning from the JMP DETOK ; subroutine using a tail call ; ****************************************************************************** ; ; Name: MT18 ; Type: Subroutine ; Category: Text ; Summary: Print a random 1-8 letter word in Sentence Case ; Deep dive: Extended text tokens ; ; ****************************************************************************** .MT18 JSR MT19 ; Call MT19 to capitalise the next letter (i.e. set ; Sentence Case for this word only) JSR DORND ; Set A and X to random numbers and reduce A to a AND #3 ; random number in the range 0-3 TAY ; Copy the random number into Y, so we can use Y as a ; loop counter to print 1-4 words (i.e. Y+1 words) .MT18L JSR DORND ; Set A and X to random numbers and reduce A to an even AND #62 ; random number in the range 0-62 (as bit 0 of 62 is 0) TAX ; Copy the random number into X, so X contains the table ; offset of a random extended two-letter token from 0-31 ; which we can now use to pick a token from the combined ; tables at TKN2+2 and QQ16 (we intentionally exclude ; the first token in TKN2, which contains a newline) LDA TKN2+2,X ; Print the first letter of the token at TKN2+2 + X JSR DTS LDA TKN2+3,X ; Print the second letter of the token at TKN2+2 + X JSR DTS DEY ; Decrement the loop counter BPL MT18L ; Loop back to MT18L to print another two-letter token ; until we have printed Y+1 of them RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MT19 ; Type: Subroutine ; Category: Text ; Summary: Capitalise the next letter ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This routine sets the following: ; ; * DTW8 = %11011111 (capitalise the next letter) ; ; ****************************************************************************** .MT19 LDA #%11011111 ; Set DTW8 = %11011111 STA DTW8 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: VOWEL ; Type: Subroutine ; Category: Text ; Summary: Test whether a character is a vowel ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The character to be tested ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag The C flag is set if the character is a vowel, otherwise ; it is clear ; ; ****************************************************************************** .VOWEL ORA #%00100000 ; Set bit 5 of the character to make it lower case CMP #'a' ; If the letter is a vowel, jump to VRTS to return from BEQ VRTS ; the subroutine with the C flag set (as the CMP will CMP #'e' ; set the C flag if the comparison is equal) BEQ VRTS CMP #'i' BEQ VRTS CMP #'o' BEQ VRTS CMP #'u' BEQ VRTS CLC ; The character is not a vowel, so clear the C flag .VRTS RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: WHITETEXT ; Type: Subroutine ; Category: Text ; Summary: Switch to white text ; ; ------------------------------------------------------------------------------ ; ; This subroutine has no effect in this version of Elite. ; ; ****************************************************************************** .WHITETEXT ;LDA #32 ; These instructions are commented out in the original ;JSR DOVDU19 ; source ; ;LDA #RED ;JMP DOCOL RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: JMTB ; Type: Variable ; Category: Text ; Summary: The extended token table for jump tokens 1-32 (DETOK) ; Deep dive: Extended text tokens ; ; ****************************************************************************** .JMTB EQUW MT1 ; Token 1: Switch to ALL CAPS EQUW MT2 ; Token 2: Switch to Sentence Case EQUW TT27 ; Token 3: Print the selected system name EQUW TT27 ; Token 4: Print the commander's name EQUW MT5 ; Token 5: Switch to extended tokens EQUW MT6 ; Token 6: Switch to standard tokens, in Sentence Case EQUW DASC ; Token 7: Beep EQUW MT8 ; Token 8: Tab to column 6 EQUW MT9 ; Token 9: Clear screen, tab to column 1, view type = 1 EQUW DASC ; Token 10: Line feed EQUW NLIN4 ; Token 11: Draw box around title (line at pixel row 19) EQUW DASC ; Token 12: Carriage return EQUW MT13 ; Token 13: Switch to lower case EQUW MT14 ; Token 14: Switch to justified text EQUW MT15 ; Token 15: Switch to left-aligned text EQUW MT16 ; Token 16: Print the character in DTW7 (drive number) EQUW MT17 ; Token 17: Print system name adjective in Sentence Case EQUW MT18 ; Token 18: Randomly print 1 to 4 two-letter tokens EQUW MT19 ; Token 19: Capitalise first letter of next word only EQUW DASC ; Token 20: Unused EQUW CLYNS ; Token 21: Clear the bottom few lines of the space view EQUW PAUSE ; Token 22: Display ship and wait for key press EQUW MT23 ; Token 23: Move to row 10, white text, set lower case EQUW PAUSE2 ; Token 24: Wait for a key press EQUW BRIS ; Token 25: Show incoming message screen, wait 2 seconds EQUW MT26 ; Token 26: Fetch line input from keyboard (filename) EQUW MT27 ; Token 27: Print mission captain's name (217-219) EQUW MT28 ; Token 28: Print mission 1 location hint (220-221) EQUW MT29 ; Token 29: Column 6, white text, lower case in words EQUW FILEPR ; Token 30: Display currently selected media (disk/tape) EQUW OTHERFILEPR ; Token 31: Display the non-selected media (disk/tape) EQUW DASC ; Token 32: Unused ; ****************************************************************************** ; ; Name: TKN2 ; Type: Variable ; Category: Text ; Summary: The extended two-letter token lookup table ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; Two-letter token lookup table for extended tokens 215-227. ; ; ****************************************************************************** .TKN2 EQUB 12, 10 ; Token 215 = {crlf} EQUS "AB" ; Token 216 EQUS "OU" ; Token 217 EQUS "SE" ; Token 218 EQUS "IT" ; Token 219 EQUS "IL" ; Token 220 EQUS "ET" ; Token 221 EQUS "ST" ; Token 222 EQUS "ON" ; Token 223 EQUS "LO" ; Token 224 EQUS "NU" ; Token 225 EQUS "TH" ; Token 226 EQUS "NO" ; Token 227 ; ****************************************************************************** ; ; Name: QQ16 ; Type: Variable ; Category: Text ; Summary: The two-letter token lookup table ; Deep dive: Printing text tokens ; ; ------------------------------------------------------------------------------ ; ; Two-letter token lookup table for tokens 128-159. ; ; These two-letter tokens can also be used in the extended text token system, by ; adding 100 to the token number. So the extended two-letter token 228 is "AL", ; the same as the standard two-letter token 128. In this system, the last four ; tokens are not available, as they would have numbers greater than 255. ; ; ****************************************************************************** .QQ16 EQUS "AL" ; Token 128 EQUS "LE" ; Token 129 EQUS "XE" ; Token 130 EQUS "GE" ; Token 131 EQUS "ZA" ; Token 132 EQUS "CE" ; Token 133 EQUS "BI" ; Token 134 EQUS "SO" ; Token 135 EQUS "US" ; Token 136 EQUS "ES" ; Token 137 EQUS "AR" ; Token 138 EQUS "MA" ; Token 139 EQUS "IN" ; Token 140 EQUS "DI" ; Token 141 EQUS "RE" ; Token 142 EQUS "A?" ; Token 143 EQUS "ER" ; Token 144 EQUS "AT" ; Token 145 EQUS "EN" ; Token 146 EQUS "BE" ; Token 147 EQUS "RA" ; Token 148 EQUS "LA" ; Token 149 EQUS "VE" ; Token 150 EQUS "TI" ; Token 151 EQUS "ED" ; Token 152 EQUS "OR" ; Token 153 EQUS "QU" ; Token 154 EQUS "AN" ; Token 155 EQUS "TE" ; Token 156 EQUS "IS" ; Token 157 EQUS "RI" ; Token 158 EQUS "ON" ; Token 159 ; ****************************************************************************** ; ; Name: NA% ; Type: Variable ; Category: Save and load ; Summary: The data block for the last saved commander ; ; ****************************************************************************** EQUS ":0.E." ; The drive part of this string (the "0") is updated ; with the chosen drive in the GTNMEW routine, but the ; directory part (the "E") is fixed. The variable is ; followed directly by the commander file at NA%, which ; starts with the commander name, so the full string at ; NA%-5 is in the format ":0.E.jameson", which gives the ; full filename of the commander file .NA% EQUS "jameson" ; The current commander name EQUB 13 SKIP 53 ; Placeholders for bytes #0 to #52 EQUB 16 ; AVL+0 = Market availability of food, #53 EQUB 15 ; AVL+1 = Market availability of textiles, #54 EQUB 17 ; AVL+2 = Market availability of radioactives, #55 EQUB 0 ; AVL+3 = Market availability of slaves, #56 EQUB 3 ; AVL+4 = Market availability of liquor/Wines, #57 EQUB 28 ; AVL+5 = Market availability of luxuries, #58 EQUB 14 ; AVL+6 = Market availability of narcotics, #59 EQUB 0 ; AVL+7 = Market availability of computers, #60 EQUB 0 ; AVL+8 = Market availability of machinery, #61 EQUB 10 ; AVL+9 = Market availability of alloys, #62 EQUB 0 ; AVL+10 = Market availability of firearms, #63 EQUB 17 ; AVL+11 = Market availability of furs, #64 EQUB 58 ; AVL+12 = Market availability of minerals, #65 EQUB 7 ; AVL+13 = Market availability of gold, #66 EQUB 9 ; AVL+14 = Market availability of platinum, #67 EQUB 8 ; AVL+15 = Market availability of gem-stones, #68 EQUB 0 ; AVL+16 = Market availability of alien items, #69 SKIP 3 ; Placeholders for bytes #70 to #72 EQUB 128 ; SVC = Save count, #73 ; ****************************************************************************** ; ; Name: CHK2 ; Type: Variable ; Category: Save and load ; Summary: Second checksum byte for the saved commander data file ; Deep dive: Commander save files ; The competition code ; ; ------------------------------------------------------------------------------ ; ; Second commander checksum byte. If the default commander is changed, a new ; checksum will be calculated and inserted by the elite-checksum.py script. ; ; The offset of this byte within a saved commander file is also shown (it's at ; byte #74). ; ; ****************************************************************************** .CHK2 EQUB 0 ; Placeholder for the checksum in byte #74 ; ****************************************************************************** ; ; Name: CHK3 ; Type: Variable ; Category: Save and load ; Summary: Third checksum byte for the saved commander data file ; Deep dive: Commander save files ; The competition code ; ; ------------------------------------------------------------------------------ ; ; Commander checksum byte for the Apple II and Commodore 64 versions only. If ; the default commander is changed, a new checksum will be calculated and ; inserted by the elite-checksum.py script. ; ; The offset of this byte within a saved commander file is also shown (it's at ; byte #75). ; ; ****************************************************************************** .CHK3 EQUB 0 ; Placeholder for the checksum in byte #75 ; ****************************************************************************** ; ; Name: CHK ; Type: Variable ; Category: Save and load ; Summary: First checksum byte for the saved commander data file ; Deep dive: Commander save files ; The competition code ; ; ------------------------------------------------------------------------------ ; ; Commander checksum byte. If the default commander is changed, a new checksum ; will be calculated and inserted by the elite-checksum.py script. ; ; The offset of this byte within a saved commander file is also shown (it's at ; byte #76). ; ; ****************************************************************************** .CHK EQUB 0 ; Placeholder for the checksum in byte #76 SKIP 20 ; These bytes appear to be unused ; ****************************************************************************** ; ; Name: S1% ; Type: Variable ; Category: Save and load ; Summary: The drive and directory number used when saving or loading a ; commander file ; Deep dive: Commander save files ; ; ------------------------------------------------------------------------------ ; ; This is a BBC Micro drive and directory string. It is not used in this version ; of Elite and is left over from the BBC Micro version. ; ; ****************************************************************************** .S1% EQUS ":0.E." ; ****************************************************************************** ; ; Name: NA2% ; Type: Variable ; Category: Save and load ; Summary: The data block for the default commander ; Deep dive: Commander save files ; The competition code ; ; ------------------------------------------------------------------------------ ; ; Contains the default commander data, with the name at NA% and the data at ; NA%+8 onwards. The size of the data block is given in NT% (which also includes ; the two checksum bytes that follow this block). This block is initially set up ; with the default commander, which can be maxed out for testing purposes by ; setting Q% to TRUE. ; ; The commander's name is stored at NA%, and can be up to 7 characters long ; (the DFS filename limit). It is terminated with a carriage return character, ; ASCII 13. ; ; The offset of each byte within a saved commander file is also shown as #0, #1 ; and so on, so the kill tally, for example, is in bytes #71 and #72 of the ; saved file. The related variable name from the current commander block is ; also shown. ; ; ****************************************************************************** .NA2% EQUS "JAMESON" ; The current commander name, which defaults to JAMESON EQUB 13 ; ; The commander name can be up to seven characters (the ; DFS limit for filenames), and is terminated by a ; carriage return ; NA%+8 is the start of the commander data block ; ; This block contains the last saved commander data ; block. As the game is played it uses an identical ; block at location TP to store the current commander ; state, and that block is copied here when the game is ; saved. Conversely, when the game starts up, the block ; here is copied to TP, which restores the last saved ; commander when we die ; ; The initial state of this block defines the default ; commander. Q% can be set to TRUE to give the default ; commander lots of credits and equipment EQUB 0 ; TP = Mission status, #0 EQUB 20 ; QQ0 = Current system X-coordinate (Lave), #1 EQUB 173 ; QQ1 = Current system Y-coordinate (Lave), #2 EQUW $5A4A ; QQ21 = Seed s0 for system 0, galaxy 0 (Tibedied), #3-4 EQUW $0248 ; QQ21 = Seed s1 for system 0, galaxy 0 (Tibedied), #5-6 EQUW $B753 ; QQ21 = Seed s2 for system 0, galaxy 0 (Tibedied), #7-8 IF Q% EQUD $00CA9A3B ; CASH = Amount of cash (100,000,000 Cr), #9-12 ELSE EQUD $E8030000 ; CASH = Amount of cash (100 Cr), #9-12 ENDIF EQUB 70 ; QQ14 = Fuel level, #13 EQUB %10000000 AND Q% ; COK = Competition flags, #14 EQUB 0 ; GCNT = Galaxy number, 0-7, #15 IF Q% EQUB Armlas ; LASER = Front laser, #16 ELSE EQUB POW ; LASER = Front laser, #16 ENDIF EQUB POW AND Q% ; LASER = Rear laser, #16 EQUB (POW+128) AND Q% ; LASER+2 = Left laser, #18 EQUB Mlas AND Q% ; LASER+3 = Right laser, #19 EQUW 0 ; These bytes appear to be unused (they were originally ; used for up/down lasers, but they were dropped), ; #20-21 EQUB 22 + (15 AND Q%) ; CRGO = Cargo capacity, #22 EQUB 0 ; QQ20+0 = Amount of food in cargo hold, #23 EQUB 0 ; QQ20+1 = Amount of textiles in cargo hold, #24 EQUB 0 ; QQ20+2 = Amount of radioactives in cargo hold, #25 EQUB 0 ; QQ20+3 = Amount of slaves in cargo hold, #26 EQUB 0 ; QQ20+4 = Amount of liquor/Wines in cargo hold, #27 EQUB 0 ; QQ20+5 = Amount of luxuries in cargo hold, #28 EQUB 0 ; QQ20+6 = Amount of narcotics in cargo hold, #29 EQUB 0 ; QQ20+7 = Amount of computers in cargo hold, #30 EQUB 0 ; QQ20+8 = Amount of machinery in cargo hold, #31 EQUB 0 ; QQ20+9 = Amount of alloys in cargo hold, #32 EQUB 0 ; QQ20+10 = Amount of firearms in cargo hold, #33 EQUB 0 ; QQ20+11 = Amount of furs in cargo hold, #34 EQUB 0 ; QQ20+12 = Amount of minerals in cargo hold, #35 EQUB 0 ; QQ20+13 = Amount of gold in cargo hold, #36 EQUB 0 ; QQ20+14 = Amount of platinum in cargo hold, #37 EQUB 0 ; QQ20+15 = Amount of gem-stones in cargo hold, #38 EQUB 0 ; QQ20+16 = Amount of alien items in cargo hold, #39 EQUB Q% ; ECM = E.C.M. system, #40 EQUB Q% ; BST = Fuel scoops ("barrel status"), #41 EQUB Q% AND 127 ; BOMB = Energy bomb, #42 EQUB Q% AND 1 ; ENGY = Energy/shield level, #43 EQUB Q% ; DKCMP = Docking computer, #44 EQUB Q% ; GHYP = Galactic hyperdrive, #45 EQUB Q% ; ESCP = Escape pod, #46 EQUD 0 ; These four bytes appear to be unused, #47-50 EQUB 3 + (Q% AND 1) ; NOMSL = Number of missiles, #51 EQUB 0 ; FIST = Legal status ("fugitive/innocent status"), #52 EQUB 16 ; AVL+0 = Market availability of food, #53 EQUB 15 ; AVL+1 = Market availability of textiles, #54 EQUB 17 ; AVL+2 = Market availability of radioactives, #55 EQUB 0 ; AVL+3 = Market availability of slaves, #56 EQUB 3 ; AVL+4 = Market availability of liquor/Wines, #57 EQUB 28 ; AVL+5 = Market availability of luxuries, #58 EQUB 14 ; AVL+6 = Market availability of narcotics, #59 EQUB 0 ; AVL+7 = Market availability of computers, #60 EQUB 0 ; AVL+8 = Market availability of machinery, #61 EQUB 10 ; AVL+9 = Market availability of alloys, #62 EQUB 0 ; AVL+10 = Market availability of firearms, #63 EQUB 17 ; AVL+11 = Market availability of furs, #64 EQUB 58 ; AVL+12 = Market availability of minerals, #65 EQUB 7 ; AVL+13 = Market availability of gold, #66 EQUB 9 ; AVL+14 = Market availability of platinum, #67 EQUB 8 ; AVL+15 = Market availability of gem-stones, #68 EQUB 0 ; AVL+16 = Market availability of alien items, #69 EQUB 0 ; QQ26 = Random byte that changes for each visit to a ; system, for randomising market prices, #70 EQUW 20000 AND Q% ; TALLY = Number of kills, #71-72 EQUB 128 ; SVC = Save count, #73 ;.CHK2 ; This label is commented out in the original source EQUB $AA ; The CHK2 checksum value for the default commander ;.CHK3 ; This label is commented out in the original source EQUB $27 ; The CHK3 checksum value for the default commander ;.CHK ; This label is commented out in the original source EQUB $03 ; The CHK checksum value for the default commander SKIP 12 ; These bytes appear to be unused .NAEND% SKIP 4 ; These bytes appear to be unused ; ****************************************************************************** ; ; Name: scacol ; Type: Variable ; Category: Drawing ships ; Summary: Ship colours on the scanner ; Deep dive: The elusive Cougar ; ; ****************************************************************************** .scacol EQUB 0 ; This byte appears to be unused EQUB GREEN ; Missile EQUB GREEN ; Coriolis space station EQUB BLUE ; Escape pod EQUB BLUE ; Alloy plate EQUB BLUE ; Cargo canister EQUB RED ; Boulder EQUB RED ; Asteroid EQUB RED ; Splinter EQUB CYAN ; Shuttle EQUB CYAN ; Transporter EQUB CYAN ; Cobra Mk III EQUB MAG ; Python EQUB MAG ; Boa EQUB MAG ; Anaconda EQUB RED ; Rock hermit (asteroid) EQUB CYAN ; Viper EQUB CYAN ; Sidewinder EQUB CYAN ; Mamba EQUB CYAN ; Krait EQUB CYAN ; Adder EQUB CYAN ; Gecko EQUB CYAN ; Cobra Mk I EQUB BLUE ; Worm EQUB CYAN ; Cobra Mk III (pirate) EQUB CYAN ; Asp Mk II EQUB MAG ; Python (pirate) EQUB CYAN ; Fer-de-lance EQUB CYAN ; Moray EQUB WHITE ; Thargoid EQUB CYAN ; Thargon EQUB CYAN ; Constrictor EQUB 0 ; Cougar EQUB CYAN ; These bytes appear to be unused EQUD 0 ; ****************************************************************************** ; ; Name: LSX2 ; Type: Variable ; Category: Drawing lines ; Summary: The ball line heap for storing x-coordinates ; Deep dive: The ball line heap ; ; ****************************************************************************** .LSX2 IF _MATCH_ORIGINAL_BINARIES IF _GMA_RELEASE EQUB $76, $85, $9C, $A5, $8B, $85, $9A, $A5 ; These bytes appear to be EQUB $8D, $20, $0C, $9A, $B0, $D2, $85, $6F ; unused and just contain random EQUB $A5, $9C, $85, $70, $A5, $6B, $85, $9B ; workspace noise left over from EQUB $A5, $72, $85, $9C, $A5, $85, $85, $9A ; the BBC Micro assembly process EQUB $A5, $87, $20, $0C, $9A, $B0, $B9, $85 EQUB $6B, $A5, $9C, $85, $6C, $A5, $6D, $85 EQUB $9B, $A5, $74, $85, $9C, $A5, $88, $85 EQUB $9A, $A5, $8A, $20, $0C, $9A, $B0, $A0 EQUB $85, $6D, $A5, $9C, $85, $6E, $A5, $71 EQUB $85, $9A, $A5, $6B, $20, $EA, $39, $85 EQUB $BB, $A5, $72, $45, $6C, $85, $9C, $A5 EQUB $73, $85, $9A, $A5, $6D, $20, $EA, $39 EQUB $85, $9A, $A5, $BB, $85, $9B, $A5, $74 EQUB $45, $6E, $20, $0C, $9A, $85, $BB, $A5 EQUB $75, $85, $9A, $A5, $6F, $20, $EA, $39 EQUB $85, $9A, $A5, $BB, $85, $9B, $A5, $70 EQUB $45, $76, $20, $0C, $9A, $48, $98, $4A EQUB $4A, $AA, $68, $24, $9C, $30, $02, $A9 EQUB $00, $95, $35, $C8, $C4, $AE, $B0, $FE EQUB $4C, $F2, $9B, $A4, $47, $A6, $48, $A5 EQUB $4B, $85, $47, $A5, $4C, $85, $48, $84 EQUB $4B, $86, $4C, $A4, $49, $A6, $4A, $A5 EQUB $51, $85, $49, $A5, $52, $85, $4A, $84 EQUB $51, $86, $52, $A4, $4F, $A6, $50, $A5 EQUB $53, $85, $4F, $A5, $54, $85, $50, $84 EQUB $53, $86, $54, $A0, $08, $B1, $57, $85 EQUB $AE, $A5, $57, $18, $69, $14, $85, $5B EQUB $A5, $58, $69, $00, $85, $5C, $A0, $00 EQUB $84, $AA, $84, $9F, $B1, $5B, $85, $6B EQUB $C8, $B1, $5B, $85, $6D, $C8, $B1, $5B EQUB $85, $6F, $C8, $B1, $5B, $85, $BB, $29 EQUB $1F, $C5, $AD, $90, $FB, $C8, $B1, $5B ELIF _SOURCE_DISK EQUB $60, $6D, $A5, $8A, $85, $6E, $A5, $8B ; These bytes appear to be EQUB $85, $6F, $A5, $8D, $85, $70, $4C, $40 ; unused and just contain random EQUB $A5, $46, $85, $46, $8B, $46, $88, $A2 ; workspace noise left over from EQUB $01, $A5, $71, $85, $6B, $A5, $73, $85 ; the BBC Micro assembly process EQUB $6D, $A5, $75, $CA, $30, $FE, $46, $6B EQUB $46, $6D, $4A, $CA, $10, $F8, $85, $9B EQUB $A5, $76, $85, $9C, $A5, $8B, $85, $9A EQUB $A5, $8D, $20, $0C, $A3, $B0, $D2, $85 EQUB $6F, $A5, $9C, $85, $70, $A5, $6B, $85 EQUB $9B, $A5, $72, $85, $9C, $A5, $85, $85 EQUB $9A, $A5, $87, $20, $0C, $A3, $B0, $B9 EQUB $85, $6B, $A5, $9C, $85, $6C, $A5, $6D EQUB $85, $9B, $A5, $74, $85, $9C, $A5, $88 EQUB $85, $9A, $A5, $8A, $20, $0C, $A3, $B0 EQUB $A0, $85, $6D, $A5, $9C, $85, $6E, $A5 EQUB $71, $85, $9A, $A5, $6B, $20, $E7, $39 EQUB $85, $BB, $A5, $72, $45, $6C, $85, $9C EQUB $A5, $73, $85, $9A, $A5, $6D, $20, $E7 EQUB $39, $85, $9A, $A5, $BB, $85, $9B, $A5 EQUB $74, $45, $6E, $20, $0C, $A3, $85, $BB EQUB $A5, $75, $85, $9A, $A5, $6F, $20, $E7 EQUB $39, $85, $9A, $A5, $BB, $85, $9B, $A5 EQUB $70, $45, $76, $20, $0C, $A3, $48, $98 EQUB $4A, $4A, $AA, $68, $24, $9C, $30, $02 EQUB $A9, $00, $95, $35, $C8, $C4, $AE, $B0 EQUB $FE, $4C, $F2, $A4, $A4, $47, $A6, $48 EQUB $A5, $4B, $85, $47, $A5, $4C, $85, $48 EQUB $84, $4B, $86, $4C, $A4, $49, $A6, $4A EQUB $A5, $51, $85, $49, $A5, $52, $85, $4A EQUB $84, $51, $86, $52, $A4, $4F, $A6, $50 EQUB $A5, $53, $85, $4F, $A5, $54, $85, $50 EQUB $84, $53, $86, $54, $A0, $08, $B1, $57 ENDIF ELSE SKIP 256 ; The ball line heap for storing x-coordinates ENDIF ; ****************************************************************************** ; ; Name: LSY2 ; Type: Variable ; Category: Drawing lines ; Summary: The ball line heap for storing y-coordinates ; Deep dive: The ball line heap ; ; ****************************************************************************** .LSY2 IF _MATCH_ORIGINAL_BINARIES IF _GMA_RELEASE EQUB $85, $2E, $29, $0F, $AA, $B5, $35, $D0 ; These bytes appear to be EQUB $FE, $A5, $2E, $4A, $4A, $4A, $4A, $AA ; unused and just contain random EQUB $B5, $35, $D0, $FE, $C8, $B1, $5B, $85 ; workspace noise left over from EQUB $2E, $29, $0F, $AA, $B5, $35, $D0, $FE ; the BBC Micro assembly process EQUB $A5, $2E, $4A, $4A, $4A, $4A, $AA, $B5 EQUB $35, $D0, $FE, $4C, $8E, $9D, $A5, $BB EQUB $85, $6C, $0A, $85, $6E, $0A, $85, $70 EQUB $20, $2C, $9A, $A5, $0B, $85, $6D, $45 EQUB $72, $30, $FE, $18, $A5, $71, $65, $09 EQUB $85, $6B, $A5, $0A, $69, $00, $85, $6C EQUB $4C, $B3, $9D, $A5, $09, $38, $E5, $71 EQUB $85, $6B, $A5, $0A, $E9, $00, $85, $6C EQUB $B0, $FE, $49, $FF, $85, $6C, $A9, $01 EQUB $E5, $6B, $85, $6B, $90, $02, $E6, $6C EQUB $A5, $6D, $49, $80, $85, $6D, $A5, $0E EQUB $85, $70, $45, $74, $30, $FE, $18, $A5 EQUB $73, $65, $0C, $85, $6E, $A5, $0D, $69 EQUB $00, $85, $6F, $4C, $EE, $9D, $A5, $0C EQUB $38, $E5, $73, $85, $6E, $A5, $0D, $E9 EQUB $00, $85, $6F, $B0, $FE, $49, $FF, $85 EQUB $6F, $A5, $6E, $49, $FF, $69, $01, $85 EQUB $6E, $A5, $70, $49, $80, $85, $70, $90 EQUB $FE, $E6, $6F, $A5, $76, $30, $FE, $A5 EQUB $75, $18, $65, $0F, $85, $BB, $A5, $10 EQUB $69, $00, $85, $99, $4C, $27, $9E, $A6 EQUB $9A, $F0, $FE, $A2, $00, $4A, $E8, $C5 EQUB $9A, $B0, $FA, $86, $9C, $20, $AF, $99 EQUB $A6, $9C, $A5, $9B, $0A, $26, $99, $30 EQUB $FE, $CA, $D0, $F8, $85, $9B, $60, $A9 EQUB $32, $85, $9B, $85, $99, $60, $A9, $80 EQUB $38, $E5, $9B, $9D, $00, $01, $E8, $A9 EQUB $00, $E5, $99, $9D, $00, $01, $4C, $61 ELIF _SOURCE_DISK EQUB $85, $AE, $A5, $57, $18, $69, $14, $85 ; These bytes appear to be EQUB $5B, $A5, $58, $69, $00, $85, $5C, $A0 ; unused and just contain random EQUB $00, $84, $AA, $84, $9F, $B1, $5B, $85 ; workspace noise left over from EQUB $6B, $C8, $B1, $5B, $85, $6D, $C8, $B1 ; the BBC Micro assembly process EQUB $5B, $85, $6F, $C8, $B1, $5B, $85, $BB EQUB $29, $1F, $C5, $AD, $90, $FB, $C8, $B1 EQUB $5B, $85, $2E, $29, $0F, $AA, $B5, $35 EQUB $D0, $FE, $A5, $2E, $4A, $4A, $4A, $4A EQUB $AA, $B5, $35, $D0, $FE, $C8, $B1, $5B EQUB $85, $2E, $29, $0F, $AA, $B5, $35, $D0 EQUB $FE, $A5, $2E, $4A, $4A, $4A, $4A, $AA EQUB $B5, $35, $D0, $FE, $4C, $8E, $A6, $A5 EQUB $BB, $85, $6C, $0A, $85, $6E, $0A, $85 EQUB $70, $20, $2C, $A3, $A5, $0B, $85, $6D EQUB $45, $72, $30, $FE, $18, $A5, $71, $65 EQUB $09, $85, $6B, $A5, $0A, $69, $00, $85 EQUB $6C, $4C, $B3, $A6, $A5, $09, $38, $E5 EQUB $71, $85, $6B, $A5, $0A, $E9, $00, $85 EQUB $6C, $B0, $FE, $49, $FF, $85, $6C, $A9 EQUB $01, $E5, $6B, $85, $6B, $90, $02, $E6 EQUB $6C, $A5, $6D, $49, $80, $85, $6D, $A5 EQUB $0E, $85, $70, $45, $74, $30, $FE, $18 EQUB $A5, $73, $65, $0C, $85, $6E, $A5, $0D EQUB $69, $00, $85, $6F, $4C, $EE, $A6, $A5 EQUB $0C, $38, $E5, $73, $85, $6E, $A5, $0D EQUB $E9, $00, $85, $6F, $B0, $FE, $49, $FF EQUB $85, $6F, $A5, $6E, $49, $FF, $69, $01 EQUB $85, $6E, $A5, $70, $49, $80, $85, $70 EQUB $90, $FE, $E6, $6F, $A5, $76, $30, $FE EQUB $A5, $75, $18, $65, $0F, $85, $BB, $A5 EQUB $10, $69, $00, $85, $99, $4C, $27, $A7 EQUB $A6, $9A, $F0, $FE, $A2, $00, $4A, $E8 ENDIF ELSE SKIP 256 ; The ball line heap for storing y-coordinates ENDIF ; ****************************************************************************** ; ; Save ELTA.bin ; ; ****************************************************************************** PRINT "ELITE A" PRINT "Assembled at ", ~CODE% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_A% PRINT "S.ELTA ", ~CODE%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_A% SAVE "3-assembled-output/ELTA.bin", CODE%, P%, LOAD% ; ****************************************************************************** ; ; ELITE B FILE ; ; Produces the binary file ELTB.bin that gets loaded by elite-checksum.py. ; ; ****************************************************************************** CODE_B% = P% LOAD_B% = LOAD% + P% - CODE% ; ****************************************************************************** ; ; Name: UNIV ; Type: Variable ; Category: Universe ; Summary: Table of pointers to the local universe's ship data blocks ; Deep dive: The local bubble of universe ; Ship data blocks ; ; ****************************************************************************** .UNIV FOR I%, 0, NOSH EQUW K% + I% * NI% ; Address of block no. I%, of size NI%, in workspace K% NEXT EQUD $10204080 ; These bytes appear to be unused; they contain a copy EQUD $01020408 ; of the TWOS variable, and the original source has a EQUW $4080 ; commented out label .TWOS EQUD $030C30C0 ; These bytes appear to be unused; they contain a copy ; of the DTWOS variable, and the original source has a ; commented out label .DTWOS ; ****************************************************************************** ; ; Name: TWOS2 ; Type: Variable ; Category: Drawing pixels ; Summary: Ready-made double-pixel character row bytes for the space view ; Deep dive: Drawing pixels in the Commodore 64 version ; ; ------------------------------------------------------------------------------ ; ; Ready-made bytes for plotting two-pixel points in the space view. See the ; PIXEL routine for details. ; ; ****************************************************************************** .TWOS2 EQUB %11000000 EQUB %11000000 EQUB %01100000 EQUB %00110000 EQUB %00011000 EQUB %00001100 EQUB %00000110 EQUB %00000011 ; ****************************************************************************** ; ; Name: CTWOS ; Type: Variable ; Category: Drawing pixels ; Summary: Ready-made double-pixel character row bytes for the dashboard ; ; ------------------------------------------------------------------------------ ; ; Ready-made bytes for plotting two-pixel points in the dashboard (the bottom ; part of the screen). ; ; There is one extra row to support the use of CTWOS+1,X indexing in the CPIX2 ; routine. The extra row is a repeat of the first row, and saves us from having ; to work out whether CTWOS+1+X needs to be wrapped around when drawing a ; two-pixel dash that crosses from one character block into another. See CPIX2 ; for more details. ; ; ****************************************************************************** .CTWOS EQUB %11000000 EQUB %00110000 EQUB %00001100 EQUB %00000011 EQUB %11000000 ; ****************************************************************************** ; ; Name: FLKB ; Type: Subroutine ; Category: Keyboard ; Summary: Flush the keyboard buffer ; ; ------------------------------------------------------------------------------ ; ; This routine does nothing in Commodore 64 Elite, apart from setting the A and ; X registers to 15. This code is left over from the 6502 Second Processor ; version of Elite. ; ; ****************************************************************************** .FLKB LDA #15 ; This code is left over from the 6502 Second Processor TAX ; version, which calls OSBYTE to flush the input buffers ;JMP OSBYTE ; (the call to OSBYTE is commented out in the original ; source, so all this does now is set A and X to 15) RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: NLIN3 ; Type: Subroutine ; Category: Drawing lines ; Summary: Print a title and draw a horizontal line at row 19 to box it in ; ; ------------------------------------------------------------------------------ ; ; This routine print a text token at the cursor position and draws a horizontal ; line at pixel row 19. It is used for the Status Mode screen, the Short-range ; Chart, the Market Price screen and the Equip Ship screen. ; ; ****************************************************************************** .NLIN3 JSR TT27 ; Print the text token in A ; Fall through into NLIN4 to draw a horizontal line at ; pixel row 19 ; ****************************************************************************** ; ; Name: NLIN4 ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a horizontal line at pixel row 19 to box in a title ; ; ------------------------------------------------------------------------------ ; ; This routine is used on the Inventory screen to draw a horizontal line at ; pixel row 19 to box in the title. ; ; ****************************************************************************** .NLIN4 LDA #19 ; Jump to NLIN2 to draw a horizontal line at pixel row BNE NLIN2 ; 19, returning from the subroutine with using a tail ; call (this BNE is effectively a JMP as A will never ; be zero) ; ****************************************************************************** ; ; Name: NLIN ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a horizontal line at pixel row 23 to box in a title ; ; ------------------------------------------------------------------------------ ; ; Draw a horizontal line at pixel row 23 and move the text cursor down one ; line. ; ; ****************************************************************************** .NLIN LDA #23 ; Set A = 23 so NLIN2 below draws a horizontal line at ; pixel row 23 JSR INCYC ; Move the text cursor down one line ; Fall through into NLIN2 to draw the horizontal line ; at row 23 ; ****************************************************************************** ; ; Name: NLIN2 ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a screen-wide horizontal line at the pixel row in A ; ; ------------------------------------------------------------------------------ ; ; This draws a line from (0, A) to (255, A), which runs across the whole screen. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The pixel row on which to draw the horizontal line ; ; ****************************************************************************** .NLIN2 STA Y1 ; Set Y1 = A STA Y2 ; Set Y2 = A ;LDA #YELLOW ; These instructions are commented out in the original ;JSR DOCOL ; source LDX #0 ; Set X1 = 0, so (X1, Y1) = (0, A) STX X1 DEX ; Set X2 = 255, so (X2, Y2) = (255, A) STX X2 JMP LL30 ; Call LL30 to draw a line from (0, A) to (255, A), ; returning from the subroutine using a tail call ;LDA #CYAN ; These instructions are commented out in the original ;JMP DOCOL ; source ; ;RTS ; ****************************************************************************** ; ; Name: HLOIN2 ; Type: Subroutine ; Category: Drawing lines ; Summary: Remove a line from the sun line heap and draw it on-screen ; ; ------------------------------------------------------------------------------ ; ; Specifically, this does the following: ; ; * Set X1 and X2 to the x-coordinates of the ends of the horizontal line with ; centre YY(1 0) and length A to the left and right ; ; * Set the Y-th byte of the LSO block to 0 (i.e. remove this line from the ; sun line heap) ; ; * Draw a horizontal line from (X1, Y) to (X2, Y) ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; YY(1 0) The x-coordinate of the centre point of the line ; ; A The half-width of the line, i.e. the contents of the ; Y-th byte of the sun line heap ; ; Y The number of the entry in the sun line heap (which is ; also the y-coordinate of the line) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Y Y is preserved ; ; ****************************************************************************** .HLOIN2 JSR EDGES ; Call EDGES to calculate X1 and X2 for the horizontal ; line centred on YY(1 0) and with half-width A STY Y1 ; Set Y1 = Y LDA #0 ; Set the Y-th byte of the LSO block to 0 STA LSO,Y JMP HLOIN ; Call HLOIN to draw a horizontal line from (X1, Y) to ; (X2, Y), returning from the subroutine using a tail ; call ; ****************************************************************************** ; ; Name: TWFL ; Type: Variable ; Category: Drawing lines ; Summary: Ready-made character rows for the left end of a horizontal line in ; the space view ; ; ------------------------------------------------------------------------------ ; ; Ready-made bytes for plotting horizontal line end caps in the space view. This ; table provides a byte with pixels at the left end, which is used for the right ; end of the line. ; ; See the HLOIN routine for details. ; ; ****************************************************************************** .TWFL EQUB %10000000 EQUB %11000000 EQUB %11100000 EQUB %11110000 EQUB %11111000 EQUB %11111100 EQUB %11111110 ; ****************************************************************************** ; ; Name: TWFR ; Type: Variable ; Category: Drawing lines ; Summary: Ready-made character rows for the right end of a horizontal line ; in the space view ; ; ------------------------------------------------------------------------------ ; ; Ready-made bytes for plotting horizontal line end caps in the space view. This ; table provides a byte with pixels at the right end, which is used for the left ; end of the line. ; ; See the HLOIN routine for details. ; ; ****************************************************************************** .TWFR EQUB %11111111 EQUB %01111111 EQUB %00111111 EQUB %00011111 EQUB %00001111 EQUB %00000111 EQUB %00000011 EQUB %00000001 ; ****************************************************************************** ; ; Name: PIX1 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (YY+1 SYL+Y) = (A P) + (S R) and draw stardust particle ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; (YY+1 SYL+Y) = (A P) + (S R) ; ; and draw a stardust particle at (X1,Y1) with distance ZZ. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; (A P) A is the angle ALPHA or BETA, P is always 0 ; ; (S R) YY(1 0) or YY(1 0) + Q * A ; ; Y Stardust particle number ; ; X1 The x-coordinate offset ; ; Y1 The y-coordinate offset ; ; ZZ The distance of the point, with bigger distances drawing ; smaller points: ; ; * ZZ < 80 Double-height four-pixel square ; ; * 80 <= ZZ <= 143 Single-height two-pixel dash ; ; * ZZ > 143 Single-height one-pixel dot ; ; ****************************************************************************** .PIX1 JSR ADD ; Set (A X) = (A P) + (S R) STA YY+1 ; Set YY+1 to A, the high byte of the result TXA ; Set SYL+Y to X, the low byte of the result STA SYL,Y ; Fall through into PIXEL2 to draw the stardust particle ; at (X1,Y1) ; ****************************************************************************** ; ; Name: PIXEL2 ; Type: Subroutine ; Category: Drawing pixels ; Summary: Draw a stardust particle relative to the screen centre ; ; ------------------------------------------------------------------------------ ; ; Draw a point (X1, Y1) from the middle of the screen with a size determined by ; a distance value. Used to draw stardust particles. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X1 The x-coordinate offset ; ; Y1 The y-coordinate offset (positive means up the screen ; from the centre, negative means down the screen) ; ; ZZ The distance of the point, with bigger distances drawing ; smaller points: ; ; * ZZ < 80 Double-height four-pixel square ; ; * 80 <= ZZ <= 143 Single-height two-pixel dash ; ; * ZZ > 143 Single-height one-pixel dot ; ; ****************************************************************************** .PIXEL2 LDA X1 ; Fetch the x-coordinate offset into A BPL PX1 ; If the x-coordinate offset is positive, jump to PX1 ; to skip the following negation EOR #%01111111 ; The x-coordinate offset is negative, so flip all the CLC ; bits apart from the sign bit and add 1, to convert it ADC #1 ; from a sign-magnitude number to a signed number .PX1 EOR #%10000000 ; Set X = X1 + 128 TAX ; ; So X is now the offset converted to an x-coordinate, ; centred on x-coordinate 128 LDA Y1 ; Fetch the y-coordinate offset into A and clear the AND #%01111111 ; sign bit, so A = |Y1| CMP #Y ; If |Y1| >= #Y then it's off the screen (as #Y is half BCS PX4 ; the screen height), so return from the subroutine (as ; PX4 contains an RTS) LDA Y1 ; Fetch the y-coordinate offset into A BPL PX2 ; If the y-coordinate offset is positive, jump to PX2 ; to skip the following negation EOR #%01111111 ; The y-coordinate offset is negative, so flip all the ADC #1 ; bits apart from the sign bit and subtract 1, to negate ; it to a positive number, i.e. A is now |Y1| .PX2 STA T ; Set A = #Y + 1 - Y1 LDA #Y+1 ; SBC T ; So if Y1 is positive we display the point up from the ; centre at y-coordinate 97, while a negative Y1 means ; down from the centre ; Fall through into PIXEL to draw the stardust at the ; screen coordinates in (X, A) ; ****************************************************************************** ; ; Name: PIXEL ; Type: Subroutine ; Category: Drawing pixels ; Summary: Draw a one-pixel dot, two-pixel dash or four-pixel square ; Deep dive: Drawing pixels in the Commodore 64 version ; ; ------------------------------------------------------------------------------ ; ; Draw a point at screen coordinate (X, A) with the point size determined by the ; distance in ZZ. This applies to the top part of the screen. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The screen x-coordinate of the point to draw ; ; A The screen y-coordinate of the point to draw ; ; ZZ The distance of the point, with bigger distances drawing ; smaller points: ; ; * ZZ < 80 Double-height four-pixel square ; ; * 80 <= ZZ <= 143 Single-height two-pixel dash ; ; * ZZ > 143 Single-height one-pixel dot ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Y Y is preserved ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; PX4 Contains an RTS ; ; ****************************************************************************** .PIXEL STY T1 ; Store Y in T1 so we can restore it at the end of the ; subroutine TAY ; Copy the screen y-coordinate from A into Y TXA ; Each character block contains 8 pixel rows, so to get AND #%11111000 ; the address of the first byte in the character block ; that we need to draw into, as an offset from the start ; of the row, we clear bits 0-2 CLC ; The ylookup table lets us look up the 16-bit address ADC ylookupl,Y ; of the start of a character row containing a specific STA SC ; pixel, so this fetches the address for the start of LDA ylookuph,Y ; the character row containing the y-coordinate in Y, ADC #0 ; and adds it to the row offset we just calculated in A STA SC+1 ; So SC(1 0) now contains the address of the first pixel ; in the character block containing the (x, y) TYA ; Set Y = Y mod 8, which is the pixel row within the AND #7 ; character block at which we want to draw our pixel TAY ; (as each character block has 8 rows) TXA ; Set X = X mod 8, which is the horizontal pixel number AND #7 ; within the character block where the pixel lies (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA ZZ ; If distance in ZZ >= 144, then this point is a very CMP #144 ; long way away, so jump to PX3 to fetch a one-pixel BCS PX3 ; point from TWOS and EOR it into SC+Y LDA TWOS2,X ; Otherwise fetch a two-pixel dash from TWOS2 and EOR it EOR (SC),Y ; into SC+Y STA (SC),Y LDA ZZ ; If distance in ZZ >= 80, then this point is a medium CMP #80 ; distance away, so jump to PX13 to stop drawing, as a BCS PX13 ; two-pixel dash is enough ; Otherwise we keep going to draw another 2 pixel point ; either above or below the one we just drew, to make a ; four-pixel square DEY ; Reduce Y by 1 to point to the pixel row above the one BPL PX3 ; we just plotted, and if it is still positive, jump to ; PX3 to draw our second two-pixel dash LDY #1 ; Reducing Y by 1 made it negative, which means Y was ; 0 before we did the DEY above, so set Y to 1 to point ; to the pixel row after the one we just plotted .PX3 LDA TWOS2,X ; Fetch a two-pixel dash from TWOS2 and EOR it into this EOR (SC),Y ; second row to make a four-pixel square STA (SC),Y .PX13 LDY T1 ; Restore Y from T1, so Y is preserved by the routine .PX4 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: BLINE ; Type: Subroutine ; Category: Drawing circles ; Summary: Draw a circle segment and add it to the ball line heap ; Deep dive: The ball line heap ; Drawing circles ; ; ------------------------------------------------------------------------------ ; ; Draw a single segment of a circle, adding the point to the ball line heap. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; CNT The number of this segment ; ; STP The step size for the circle ; ; K6(1 0) The x-coordinate of the new point on the circle, as ; a screen coordinate ; ; (T X) The y-coordinate of the new point on the circle, as ; an offset from the centre of the circle ; ; FLAG Set to $FF for the first call, so it sets up the first ; point in the heap but waits until the second call before ; drawing anything (as we need two points, i.e. two calls, ; before we can draw a line) ; ; K4(1 0) Pixel y-coordinate of the centre of the circle ; ; K5(1 0) Screen x-coordinate of the previous point added to the ; ball line heap (if this is not the first point) ; ; K5(3 2) Screen y-coordinate of the previous point added to the ; ball line heap (if this is not the first point) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; CNT CNT is updated to CNT + STP ; ; A The new value of CNT ; ; K5(1 0) Screen x-coordinate of the point that we just added to ; the ball line heap ; ; K5(3 2) Screen y-coordinate of the point that we just added to ; the ball line heap ; ; FLAG Set to 0 ; ; ****************************************************************************** .BLINE TXA ; Set K6(3 2) = (T X) + K4(1 0) ADC K4 ; = y-coord of centre + y-coord of new point STA K6+2 ; LDA K4+1 ; so K6(3 2) now contains the y-coordinate of the new ADC T ; point on the circle but as a screen coordinate, to go STA K6+3 ; along with the screen x-coordinate in K6(1 0) LDA FLAG ; If FLAG = 0, jump down to BL1 BEQ BL1 INC FLAG ; Flag is $FF so this is the first call to BLINE, so ; increment FLAG to set it to 0, as then the next time ; we call BLINE it can draw the first line, from this ; point to the next .BL5 ; The following inserts a $FF marker into the LSY2 line ; heap to indicate that the next call to BLINE should ; store both the (X1, Y1) and (X2, Y2) points. We do ; this on the very first call to BLINE (when FLAG is ; $FF), and on subsequent calls if the segment does not ; fit on-screen, in which case we don't draw or store ; that segment, and we start a new segment with the next ; call to BLINE that does fit on-screen LDY LSP ; If byte LSP-1 of LSY2 = $FF, jump to BL7 to tidy up LDA #$FF ; and return from the subroutine, as the point that has CMP LSY2-1,Y ; been passed to BLINE is the start of a segment, so all BEQ BL7 ; we need to do is save the coordinate in K5, without ; moving the pointer in LSP STA LSY2,Y ; Otherwise we just tried to plot a segment but it ; didn't fit on-screen, so put the $FF marker into the ; heap for this point, so the next call to BLINE starts ; a new segment INC LSP ; Increment LSP to point to the next point in the heap BNE BL7 ; Jump to BL7 to tidy up and return from the subroutine ; (this BNE is effectively a JMP, as LSP will never be ; zero) .BL1 LDA K5 ; Set XX15 = K5 = x_lo of previous point STA XX15 LDA K5+1 ; Set XX15+1 = K5+1 = x_hi of previous point STA XX15+1 LDA K5+2 ; Set XX15+2 = K5+2 = y_lo of previous point STA XX15+2 LDA K5+3 ; Set XX15+3 = K5+3 = y_hi of previous point STA XX15+3 LDA K6 ; Set XX15+4 = x_lo of new point STA XX15+4 LDA K6+1 ; Set XX15+5 = x_hi of new point STA XX15+5 LDA K6+2 ; Set XX12 = y_lo of new point STA XX12 LDA K6+3 ; Set XX12+1 = y_hi of new point STA XX12+1 JSR LL145 ; Call LL145 to see if the new line segment needs to be ; clipped to fit on-screen, returning the clipped line's ; end-points in (X1, Y1) and (X2, Y2) BCS BL5 ; If the C flag is set then the line is not visible on ; screen anyway, so jump to BL5, to avoid drawing and ; storing this line LDA SWAP ; If SWAP = 0, then we didn't have to swap the line BEQ BL9 ; coordinates around during the clipping process, so ; jump to BL9 to skip the following swap LDA X1 ; Otherwise the coordinates were swapped by the call to LDY X2 ; LL145 above, so we swap (X1, Y1) and (X2, Y2) back STA X2 ; again STY X1 LDA Y1 LDY Y2 STA Y2 STY Y1 .BL9 LDY LSP ; Set Y = LSP LDA LSY2-1,Y ; If byte LSP-1 of LSY2 is not $FF, jump down to BL8 CMP #$FF ; to skip the following (X1, Y1) code BNE BL8 ; Byte LSP-1 of LSY2 is $FF, which indicates that we ; need to store (X1, Y1) in the heap LDA X1 ; Store X1 in the LSP-th byte of LSX2 STA LSX2,Y LDA Y1 ; Store Y1 in the LSP-th byte of LSY2 STA LSY2,Y INY ; Increment Y to point to the next byte in LSX2/LSY2 .BL8 LDA X2 ; Store X2 in the LSP-th byte of LSX2 STA LSX2,Y LDA Y2 ; Store Y2 in the LSP-th byte of LSX2 STA LSY2,Y INY ; Increment Y to point to the next byte in LSX2/LSY2 STY LSP ; Update LSP to point to the same as Y JSR LOIN ; Draw a line from (X1, Y1) to (X2, Y2) LDA XX13 ; If XX13 is non-zero, jump up to BL5 to add a $FF BNE BL5 ; marker to the end of the line heap. XX13 is non-zero ; after the call to the clipping routine LL145 above if ; the end of the line was clipped, meaning the next line ; sent to BLINE can't join onto the end but has to start ; a new segment, and that's what inserting the $FF ; marker does .BL7 LDA K6 ; Copy the data for this step point from K6(3 2 1 0) STA K5 ; into K5(3 2 1 0), for use in the next call to BLINE: LDA K6+1 ; STA K5+1 ; * K5(1 0) = screen x-coordinate of this point LDA K6+2 ; STA K5+2 ; * K5(3 2) = screen y-coordinate of this point LDA K6+3 ; STA K5+3 ; They now become the "previous point" in the next call LDA CNT ; Set CNT = CNT + STP CLC ADC STP STA CNT RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: FLIP ; Type: Subroutine ; Category: Stardust ; Summary: Reflect the stardust particles in the screen diagonal and redraw ; the stardust field ; ; ------------------------------------------------------------------------------ ; ; Swap the x- and y-coordinates of all the stardust particles and draw the new ; set of particles. Called by LOOK1 when we switch views. ; ; This is a quick way of making the stardust field in the new view feel ; different without having to generate a whole new field. If you look carefully ; at the stardust field when you switch views, you can just about see that the ; new field is a reflection of the previous field in the screen diagonal, i.e. ; in the line from bottom left to top right. This is the line where x = y when ; the origin is in the middle of the screen, and positive x and y are right and ; up, which is the coordinate system we use for stardust). ; ; ****************************************************************************** .FLIP ;LDA MJ ; These instructions are commented out in the original ;BNE FLIP-1 ; source. They would have the effect of not swapping the ; stardust if we had mis-jumped into witchspace LDY NOSTM ; Set Y to the current number of stardust particles, so ; we can use it as a counter through all the stardust .FLL1 LDX SY,Y ; Copy the Y-th particle's y-coordinate from SY+Y into X LDA SX,Y ; Copy the Y-th particle's x-coordinate from SX+Y into STA Y1 ; both Y1 and the particle's y-coordinate STA SY,Y TXA ; Copy the Y-th particle's original y-coordinate into STA X1 ; both X1 and the particle's x-coordinate, so the x- and STA SX,Y ; y-coordinates are now swapped and (X1, Y1) contains ; the particle's new coordinates LDA SZ,Y ; Fetch the Y-th particle's distance from SZ+Y into ZZ STA ZZ JSR PIXEL2 ; Draw a stardust particle at (X1,Y1) with distance ZZ DEY ; Decrement the counter to point to the next particle of ; stardust BNE FLL1 ; Loop back to FLL1 until we have moved all the stardust ; particles RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: STARS ; Type: Subroutine ; Category: Stardust ; Summary: The main routine for processing the stardust ; ; ------------------------------------------------------------------------------ ; ; Called at the very end of the main flight loop. ; ; ****************************************************************************** .STARS LDX VIEW ; Load the current view into X: ; ; 0 = front ; 1 = rear ; 2 = left ; 3 = right BEQ STARS1 ; If this is view 0, jump to STARS1 to process the ; stardust for the front view DEX ; If this is view 2 or 3, jump to STARS2 (via ST11) to BNE ST11 ; process the stardust for the left or right views JMP STARS6 ; Otherwise this is the rear view, so jump to STARS6 to ; process the stardust for the rear view .ST11 JMP STARS2 ; Jump to STARS2 for the left or right views, as it's ; too far for the branch instruction above ; ****************************************************************************** ; ; Name: STARS1 ; Type: Subroutine ; Category: Stardust ; Summary: Process the stardust for the front view ; Deep dive: Stardust in the front view ; ; ------------------------------------------------------------------------------ ; ; This moves the stardust towards us according to our speed (so the dust rushes ; past us), and applies our current pitch and roll to each particle of dust, so ; the stardust moves correctly when we steer our ship. ; ; When a stardust particle rushes past us and falls off the side of the screen, ; its memory is recycled as a new particle that's positioned randomly on-screen. ; ; These are the calculations referred to in the commentary: ; ; 1. q = 64 * speed / z_hi ; 2. z = z - speed * 64 ; 3. y = y + |y_hi| * q ; 4. x = x + |x_hi| * q ; ; 5. y = y + alpha * x / 256 ; 6. x = x - alpha * y / 256 ; ; 7. x = x + 2 * (beta * y / 256) ^ 2 ; 8. y = y - beta * 256 ; ; For more information see the associated deep dive. ; ; ****************************************************************************** .STARS1 LDY NOSTM ; Set Y to the current number of stardust particles, so ; we can use it as a counter through all the stardust ; In the following, we're going to refer to the 16-bit ; space coordinates of the current particle of stardust ; (i.e. the Y-th particle) like this: ; ; x = (x_hi x_lo) ; y = (y_hi y_lo) ; z = (z_hi z_lo) ; ; These values are stored in (SX+Y SXL+Y), (SY+Y SYL+Y) ; and (SZ+Y SZL+Y) respectively .STL1 JSR DV42 ; Call DV42 to set the following: ; ; (P R) = 256 * DELTA / z_hi ; = 256 * speed / z_hi ; ; The maximum value returned is P = 2 and R = 128 (see ; DV42 for an explanation) LDA R ; Set A = R, so now: ; ; (P A) = 256 * speed / z_hi LSR P ; Rotate (P A) right by 2 places, which sets P = 0 (as P ROR A ; has a maximum value of 2) and leaves: LSR P ; ROR A ; A = 64 * speed / z_hi ORA #1 ; Make sure A is at least 1, and store it in Q, so we STA Q ; now have result 1 above: ; ; Q = 64 * speed / z_hi LDA SZL,Y ; We now calculate the following: SBC DELT4 ; STA SZL,Y ; (z_hi z_lo) = (z_hi z_lo) - DELT4(1 0) ; ; starting with the low bytes LDA SZ,Y ; And then we do the high bytes STA ZZ ; SBC DELT4+1 ; We also set ZZ to the original value of z_hi, which we STA SZ,Y ; use below to remove the existing particle ; ; So now we have result 2 above: ; ; z = z - DELT4(1 0) ; = z - speed * 64 JSR MLU1 ; Call MLU1 to set: ; ; Y1 = y_hi ; ; (A P) = |y_hi| * Q ; ; So Y1 contains the original value of y_hi, which we ; use below to remove the existing particle ; We now calculate: ; ; (S R) = YY(1 0) = (A P) + y STA YY+1 ; First we do the low bytes with: LDA P ; ADC SYL,Y ; YY+1 = A STA YY ; R = YY = P + y_lo STA R ; ; so we get this: ; ; (? R) = YY(1 0) = (A P) + y_lo LDA Y1 ; And then we do the high bytes with: ADC YY+1 ; STA YY+1 ; S = YY+1 = y_hi + YY+1 STA S ; ; so we get our result: ; ; (S R) = YY(1 0) = (A P) + (y_hi y_lo) ; = |y_hi| * Q + y ; ; which is result 3 above, and (S R) is set to the new ; value of y LDA SX,Y ; Set X1 = A = x_hi STA X1 ; ; So X1 contains the original value of x_hi, which we ; use below to remove the existing particle JSR MLU2 ; Set (A P) = |x_hi| * Q ; We now calculate: ; ; XX(1 0) = (A P) + x STA XX+1 ; First we do the low bytes: LDA P ; ADC SXL,Y ; XX(1 0) = (A P) + x_lo STA XX LDA X1 ; And then we do the high bytes: ADC XX+1 ; STA XX+1 ; XX(1 0) = XX(1 0) + (x_hi 0) ; ; so we get our result: ; ; XX(1 0) = (A P) + x ; = |x_hi| * Q + x ; ; which is result 4 above, and we also have: ; ; A = XX+1 = (|x_hi| * Q + x) / 256 ; ; i.e. A is the new value of x, divided by 256 EOR ALP2+1 ; EOR with the flipped sign of the roll angle alpha, so ; A has the opposite sign to the flipped roll angle ; alpha, i.e. it gets the same sign as alpha JSR MLS1 ; Call MLS1 to calculate: ; ; (A P) = A * ALP1 ; = (x / 256) * alpha JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = (x / 256) * alpha + y ; = y + alpha * x / 256 STA YY+1 ; Set YY(1 0) = (A X) to give: STX YY ; ; YY(1 0) = y + alpha * x / 256 ; ; which is result 5 above, and we also have: ; ; A = YY+1 = y + alpha * x / 256 ; ; i.e. A is the new value of y, divided by 256 EOR ALP2 ; EOR A with the correct sign of the roll angle alpha, ; so A has the opposite sign to the roll angle alpha JSR MLS2 ; Call MLS2 to calculate: ; ; (S R) = XX(1 0) ; = x ; ; (A P) = A * ALP1 ; = -y / 256 * alpha JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = -y / 256 * alpha + x STA XX+1 ; Set XX(1 0) = (A X), which gives us result 6 above: STX XX ; ; x = x - alpha * y / 256 LDX BET1 ; Fetch the pitch magnitude into X LDA YY+1 ; Set A to y_hi and set it to the flipped sign of beta EOR BET2+1 JSR MULTS-2 ; Call MULTS-2 to calculate: ; ; (A P) = X * A ; = -beta * y_hi STA Q ; Store the high byte of the result in Q, so: ; ; Q = -beta * y_hi / 256 JSR MUT2 ; Call MUT2 to calculate: ; ; (S R) = XX(1 0) = x ; ; (A P) = Q * A ; = (-beta * y_hi / 256) * (-beta * y_hi / 256) ; = (beta * y / 256) ^ 2 ASL P ; Double (A P), store the top byte in A and set the C ROL A ; flag to bit 7 of the original A, so this does: STA T ; ; (T P) = (A P) << 1 ; = 2 * (beta * y / 256) ^ 2 LDA #0 ; Set bit 7 in A to the sign bit from the A in the ROR A ; calculation above and apply it to T, so we now have: ORA T ; ; (A P) = (A P) * 2 ; = 2 * (beta * y / 256) ^ 2 ; ; with the doubling retaining the sign of (A P) JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = 2 * (beta * y / 256) ^ 2 + x STA XX+1 ; Store the high byte A in XX+1 TXA ; Store the low byte X in x_lo STA SXL,Y ; So (XX+1 x_lo) now contains: ; ; x = x + 2 * (beta * y / 256) ^ 2 ; ; which is result 7 above LDA YY ; Set (S R) = YY(1 0) = y STA R ; LDA YY+1 ; The call to MAD and the two store instructions are ;JSR MAD ; commented out in the original source ;STA S ;STX R STA S LDA #0 ; Set P = 0 STA P LDA BETA ; Set A = -beta, so: EOR #%10000000 ; ; (A P) = (-beta 0) ; = -beta * 256 JSR PIX1 ; Call PIX1 to calculate the following: ; ; (YY+1 y_lo) = (A P) + (S R) ; = -beta * 256 + y ; ; i.e. y = y - beta * 256, which is result 8 above ; ; PIX1 also draws a particle at (X1, Y1) with distance ; ZZ, which will remove the old stardust particle, as we ; set X1, Y1 and ZZ to the original values for this ; particle during the calculations above ; We now have our newly moved stardust particle at ; x-coordinate (XX+1 x_lo) and y-coordinate (YY+1 y_lo) ; and distance z_hi, so we draw it if it's still on ; screen, otherwise we recycle it as a new bit of ; stardust and draw that LDA XX+1 ; Set X1 and x_hi to the high byte of XX in XX+1, so STA X1 ; the new x-coordinate is in (x_hi x_lo) and the high STA SX,Y ; byte is in X1 AND #%01111111 ; If |x_hi| >= 120 then jump to KILL1 to recycle this CMP #120 ; particle, as it's gone off the side of the screen, BCS KILL1 ; and rejoin at STC1 with the new particle LDA YY+1 ; Set Y1 and y_hi to the high byte of YY in YY+1, so STA SY,Y ; the new x-coordinate is in (y_hi y_lo) and the high STA Y1 ; byte is in Y1 AND #%01111111 ; If |y_hi| >= 120 then jump to KILL1 to recycle this CMP #120 ; particle, as it's gone off the top or bottom of the BCS KILL1 ; screen, and rejoin at STC1 with the new particle LDA SZ,Y ; If z_hi < 16 then jump to KILL1 to recycle this CMP #16 ; particle, as it's so close that it's effectively gone BCC KILL1 ; past us, and rejoin at STC1 with the new particle STA ZZ ; Set ZZ to the z-coordinate in z_hi .STC1 JSR PIXEL2 ; Draw a stardust particle at (X1,Y1) with distance ZZ, ; i.e. draw the newly moved particle at (x_hi, y_hi) ; with distance z_hi DEY ; Decrement the loop counter to point to the next ; stardust particle BEQ P%+5 ; If we have just done the last particle, skip the next ; instruction to return from the subroutine JMP STL1 ; We have more stardust to process, so jump back up to ; STL1 for the next particle RTS ; Return from the subroutine .KILL1 ; Our particle of stardust just flew past us, so let's ; recycle that particle, starting it at a random ; position that isn't too close to the centre point JSR DORND ; Set A and X to random numbers ORA #4 ; Make sure A is at least 4 and store it in Y1 and y_hi, STA Y1 ; so the new particle starts at least 4 pixels above or STA SY,Y ; below the centre of the screen JSR DORND ; Set A and X to random numbers ORA #8 ; Make sure A is at least 8 and store it in X1 and x_hi, STA X1 ; so the new particle starts at least 8 pixels either STA SX,Y ; side of the centre of the screen JSR DORND ; Set A and X to random numbers ORA #144 ; Make sure A is at least 144 and store it in ZZ and STA SZ,Y ; z_hi so the new particle starts in the far distance STA ZZ LDA Y1 ; Set A to the new value of y_hi. This has no effect as ; STC1 starts with a jump to PIXEL2, which starts with a ; LDA instruction JMP STC1 ; Jump up to STC1 to draw this new particle ; ****************************************************************************** ; ; Name: STARS6 ; Type: Subroutine ; Category: Stardust ; Summary: Process the stardust for the rear view ; Deep dive: Stardust in the front view ; ; ------------------------------------------------------------------------------ ; ; This routine is very similar to STARS1, which processes stardust for the front ; view. The main difference is that the direction of travel is reversed, so the ; signs in the calculations are different, as well as the order of the first ; batch of calculations. ; ; When a stardust particle falls away into the far distance, it is removed from ; the screen and its memory is recycled as a new particle, positioned randomly ; along one of the four edges of the screen. ; ; These are the calculations referred to in the commentary: ; ; 1. q = 64 * speed / z_hi ; 2. z = z - speed * 64 ; 3. y = y + |y_hi| * q ; 4. x = x + |x_hi| * q ; ; 5. y = y + alpha * x / 256 ; 6. x = x - alpha * y / 256 ; ; 7. x = x + 2 * (beta * y / 256) ^ 2 ; 8. y = y - beta * 256 ; ; For more information see the associated deep dive. ; ; ****************************************************************************** .STARS6 LDY NOSTM ; Set Y to the current number of stardust particles, so ; we can use it as a counter through all the stardust .STL6 JSR DV42 ; Call DV42 to set the following: ; ; (P R) = 256 * DELTA / z_hi ; = 256 * speed / z_hi ; ; The maximum value returned is P = 2 and R = 128 (see ; DV42 for an explanation) LDA R ; Set A = R, so now: ; ; (P A) = 256 * speed / z_hi LSR P ; Rotate (P A) right by 2 places, which sets P = 0 (as P ROR A ; has a maximum value of 2) and leaves: LSR P ; ROR A ; A = 64 * speed / z_hi ORA #1 ; Make sure A is at least 1, and store it in Q, so we STA Q ; now have result 1 above: ; ; Q = 64 * speed / z_hi LDA SX,Y ; Set X1 = A = x_hi STA X1 ; ; So X1 contains the original value of x_hi, which we ; use below to remove the existing particle JSR MLU2 ; Set (A P) = |x_hi| * Q ; We now calculate: ; ; XX(1 0) = x - (A P) STA XX+1 ; First we do the low bytes: LDA SXL,Y ; SBC P ; XX(1 0) = x_lo - (A P) STA XX LDA X1 ; And then we do the high bytes: SBC XX+1 ; STA XX+1 ; XX(1 0) = (x_hi 0) - XX(1 0) ; ; so we get our result: ; ; XX(1 0) = x - (A P) ; = x - |x_hi| * Q ; ; which is result 2 above, and we also have: JSR MLU1 ; Call MLU1 to set: ; ; Y1 = y_hi ; ; (A P) = |y_hi| * Q ; ; So Y1 contains the original value of y_hi, which we ; use below to remove the existing particle ; We now calculate: ; ; (S R) = YY(1 0) = y - (A P) STA YY+1 ; First we do the low bytes with: LDA SYL,Y ; SBC P ; YY+1 = A STA YY ; R = YY = y_lo - P STA R ; ; so we get this: ; ; (? R) = YY(1 0) = y_lo - (A P) LDA Y1 ; And then we do the high bytes with: SBC YY+1 ; STA YY+1 ; S = YY+1 = y_hi - YY+1 STA S ; ; so we get our result: ; ; (S R) = YY(1 0) = (y_hi y_lo) - (A P) ; = y - |y_hi| * Q ; ; which is result 3 above, and (S R) is set to the new ; value of y LDA SZL,Y ; We now calculate the following: ADC DELT4 ; STA SZL,Y ; (z_hi z_lo) = (z_hi z_lo) + DELT4(1 0) ; ; starting with the low bytes LDA SZ,Y ; And then we do the high bytes STA ZZ ; ADC DELT4+1 ; We also set ZZ to the original value of z_hi, which we STA SZ,Y ; use below to remove the existing particle ; ; So now we have result 4 above: ; ; z = z + DELT4(1 0) ; = z + speed * 64 LDA XX+1 ; EOR x with the correct sign of the roll angle alpha, EOR ALP2 ; so A has the opposite sign to the roll angle alpha JSR MLS1 ; Call MLS1 to calculate: ; ; (A P) = A * ALP1 ; = (-x / 256) * alpha JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = (-x / 256) * alpha + y ; = y - alpha * x / 256 STA YY+1 ; Set YY(1 0) = (A X) to give: STX YY ; ; YY(1 0) = y - alpha * x / 256 ; ; which is result 5 above, and we also have: ; ; A = YY+1 = y - alpha * x / 256 ; ; i.e. A is the new value of y, divided by 256 EOR ALP2+1 ; EOR with the flipped sign of the roll angle alpha, so ; A has the opposite sign to the flipped roll angle ; alpha, i.e. it gets the same sign as alpha JSR MLS2 ; Call MLS2 to calculate: ; ; (S R) = XX(1 0) ; = x ; ; (A P) = A * ALP1 ; = y / 256 * alpha JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = y / 256 * alpha + x STA XX+1 ; Set XX(1 0) = (A X), which gives us result 6 above: STX XX ; ; x = x + alpha * y / 256 LDA YY+1 ; Set A to y_hi and set it to the flipped sign of beta EOR BET2+1 LDX BET1 ; Fetch the pitch magnitude into X JSR MULTS-2 ; Call MULTS-2 to calculate: ; ; (A P) = X * A ; = beta * y_hi STA Q ; Store the high byte of the result in Q, so: ; ; Q = beta * y_hi / 256 LDA XX+1 ; Set S = x_hi STA S EOR #%10000000 ; Flip the sign of A, so A now contains -x JSR MUT1 ; Call MUT1 to calculate: ; ; R = XX = x_lo ; ; (A P) = Q * A ; = (beta * y_hi / 256) * (-beta * y_hi / 256) ; = (-beta * y / 256) ^ 2 ASL P ; Double (A P), store the top byte in A and set the C ROL A ; flag to bit 7 of the original A, so this does: STA T ; ; (T P) = (A P) << 1 ; = 2 * (-beta * y / 256) ^ 2 LDA #0 ; Set bit 7 in A to the sign bit from the A in the ROR A ; calculation above and apply it to T, so we now have: ORA T ; ; (A P) = -2 * (beta * y / 256) ^ 2 ; ; with the doubling retaining the sign of (A P) JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = -2 * (beta * y / 256) ^ 2 + x STA XX+1 ; Store the high byte A in XX+1 TXA ; Store the low byte X in x_lo STA SXL,Y ; So (XX+1 x_lo) now contains: ; ; x = x - 2 * (beta * y / 256) ^ 2 ; ; which is result 7 above LDA YY ; Set (S R) = YY(1 0) = y STA R LDA YY+1 STA S ;EOR #128 ; These instructions are commented out in the original ;JSR MAD ; source ;STA S ;STX R LDA #0 ; Set P = 0 STA P LDA BETA ; Set A = beta, so (A P) = (beta 0) = beta * 256 JSR PIX1 ; Call PIX1 to calculate the following: ; ; (YY+1 y_lo) = (A P) + (S R) ; = beta * 256 + y ; ; i.e. y = y + beta * 256, which is result 8 above ; ; PIX1 also draws a particle at (X1, Y1) with distance ; ZZ, which will remove the old stardust particle, as we ; set X1, Y1 and ZZ to the original values for this ; particle during the calculations above ; We now have our newly moved stardust particle at ; x-coordinate (XX+1 x_lo) and y-coordinate (YY+1 y_lo) ; and distance z_hi, so we draw it if it's still on ; screen, otherwise we recycle it as a new bit of ; stardust and draw that LDA XX+1 ; Set X1 and x_hi to the high byte of XX in XX+1, so STA X1 ; the new x-coordinate is in (x_hi x_lo) and the high STA SX,Y ; byte is in X1 LDA YY+1 ; Set Y1 and y_hi to the high byte of YY in YY+1, so STA SY,Y ; the new x-coordinate is in (y_hi y_lo) and the high STA Y1 ; byte is in Y1 AND #%01111111 ; If |y_hi| >= 110 then jump to KILL6 to recycle this CMP #110 ; particle, as it's gone off the top or bottom of the BCS KILL6 ; screen, and rejoin at STC6 with the new particle LDA SZ,Y ; If z_hi >= 160 then jump to KILL6 to recycle this CMP #160 ; particle, as it's so far away that it's too far to BCS KILL6 ; see, and rejoin at STC1 with the new particle STA ZZ ; Set ZZ to the z-coordinate in z_hi .STC6 JSR PIXEL2 ; Draw a stardust particle at (X1,Y1) with distance ZZ, ; i.e. draw the newly moved particle at (x_hi, y_hi) ; with distance z_hi DEY ; Decrement the loop counter to point to the next ; stardust particle BEQ ST3 ; If we have just done the last particle, skip the next ; instruction to return from the subroutine JMP STL6 ; We have more stardust to process, so jump back up to ; STL6 for the next particle .ST3 RTS ; Return from the subroutine .KILL6 JSR DORND ; Set A and X to random numbers AND #%01111111 ; Clear the sign bit of A to get |A| ADC #10 ; Make sure A is at least 10 and store it in z_hi and STA SZ,Y ; ZZ, so the new particle starts close to us STA ZZ LSR A ; Divide A by 2 and randomly set the C flag BCS ST4 ; Jump to ST4 half the time LSR A ; Randomly set the C flag again LDA #252 ; Set A to either +126 or -126 (252 >> 1) depending on ROR A ; the C flag, as this is a sign-magnitude number with ; the C flag rotated into its sign bit STA X1 ; Set x_hi and X1 to A, so this particle starts on STA SX,Y ; either the left or right edge of the screen JSR DORND ; Set A and X to random numbers STA Y1 ; Set y_hi and Y1 to random numbers, so the particle STA SY,Y ; starts anywhere along either the left or right edge JMP STC6 ; Jump up to STC6 to draw this new particle .ST4 JSR DORND ; Set A and X to random numbers STA X1 ; Set x_hi and X1 to random numbers, so the particle STA SX,Y ; starts anywhere along the x-axis LSR A ; Randomly set the C flag LDA #230 ; Set A to either +115 or -115 (230 >> 1) depending on ROR A ; the C flag, as this is a sign-magnitude number with ; the C flag rotated into its sign bit STA Y1 ; Set y_hi and Y1 to A, so the particle starts anywhere STA SY,Y ; along either the top or bottom edge of the screen BNE STC6 ; Jump up to STC6 to draw this new particle (this BNE is ; effectively a JMP as A will never be zero) ; ****************************************************************************** ; ; Name: MAS1 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Add an orientation vector coordinate to an INWK coordinate ; Deep dive: The space station safe zone ; ; ------------------------------------------------------------------------------ ; ; Add a doubled nosev vector coordinate, e.g. (nosev_y_hi nosev_y_lo) * 2, to ; an INWK coordinate, e.g. (x_sign x_hi x_lo), storing the result in the INWK ; coordinate. The axes used in each side of the addition are specified by the ; arguments X and Y. ; ; In the comments below, we document the routine as if we are doing the ; following, i.e. if X = 0 and Y = 11: ; ; (x_sign x_hi x_lo) = (x_sign x_hi x_lo) + (nosev_y_hi nosev_y_lo) * 2 ; ; as that way the variable names in the comments contain "x" and "y" to match ; the registers that specify the vector axis to use. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The coordinate to add, as follows: ; ; * If X = 0, add (x_sign x_hi x_lo) ; * If X = 3, add (y_sign y_hi y_lo) ; * If X = 6, add (z_sign z_hi z_lo) ; ; Y The vector to add, as follows: ; ; * If Y = 9, add (nosev_x_hi nosev_x_lo) ; * If Y = 11, add (nosev_y_hi nosev_y_lo) ; * If Y = 13, add (nosev_z_hi nosev_z_lo) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A The highest byte of the result with the sign cleared ; (e.g. |x_sign| when X = 0, etc.) ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; MA9 Contains an RTS ; ; ****************************************************************************** .MAS1 LDA INWK,Y ; Set K(2 1) = (nosev_y_hi nosev_y_lo) * 2 ASL A STA K+1 LDA INWK+1,Y ROL A STA K+2 LDA #0 ; Set K+3 bit 7 to the C flag, so the sign bit of the ROR A ; above result goes into K+3 STA K+3 JSR MVT3 ; Add (x_sign x_hi x_lo) to K(3 2 1) STA INWK+2,X ; Store the sign of the result in x_sign LDY K+1 ; Store K(2 1) in (x_hi x_lo) STY INWK,X LDY K+2 STY INWK+1,X AND #%01111111 ; Set A to the sign byte with the sign cleared, ; i.e. |x_sign| when X = 0 .MA9 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MAS2 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Calculate a cap on the maximum distance to the planet or sun ; ; ------------------------------------------------------------------------------ ; ; Given a value in Y that points to the start of a ship data block as an offset ; from K%, calculate the following: ; ; A = A OR x_sign OR y_sign OR z_sign ; ; and clear the sign bit of the result. The K% workspace contains the ship data ; blocks, so the offset in Y must be 0 or a multiple of NI% (as each block in ; K% contains NI% bytes). ; ; The result effectively contains a maximum cap of the three values (though it ; might not be one of the three input values - it's just guaranteed to be ; larger than all of them). ; ; If Y = 0 and A = 0, then this calculates the maximum cap of the highest byte ; containing the distance to the planet, as K%+2 = x_sign, K%+5 = y_sign and ; K%+8 = z_sign (the first slot in the K% workspace represents the planet). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The offset from K% for the start of the ship data block ; to use ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A OR K%+2+Y OR K%+5+Y OR K%+8+Y, with bit 7 cleared ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; m Do not include A in the calculation ; ; ****************************************************************************** .m LDA #0 ; Set A = 0 and fall through into MAS2 to calculate the ; OR of the three bytes at K%+2+Y, K%+5+Y and K%+8+Y .MAS2 ORA K%+2,Y ; Set A = A OR x_sign OR y_sign OR z_sign ORA K%+5,Y ORA K%+8,Y AND #%01111111 ; Clear bit 7 in A RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MAS3 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate A = x_hi^2 + y_hi^2 + z_hi^2 in the K% block ; ; ------------------------------------------------------------------------------ ; ; Given a value in Y that points to the start of a ship data block as an offset ; from K%, calculate the following: ; ; (A ?) = x_hi^2 + y_hi^2 + z_hi^2 ; ; returning A = $FF if the calculation overflows a one-byte result. The K% ; workspace contains the ship data blocks, so the offset in Y must be 0 or a ; multiple of NI% (as each block in K% contains NI% bytes). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The offset from K% for the start of the ship data block ; to use ; ; Returns ; ; A The high byte of x_hi^2 + y_hi^2 + z_hi^2 ; ; C flag The overflow status (i.e. did the result fit into one ; byte): ; ; * Clear if the calculation didn't overflow ; ; * Set if the calculation overflowed (in which case A ; is set to $FF) ; ; ****************************************************************************** .MAS3 LDA K%+1,Y ; Set (A P) = x_hi * x_hi JSR SQUA2 STA R ; Store A (high byte of result) in R LDA K%+4,Y ; Set (A P) = y_hi * y_hi JSR SQUA2 ADC R ; Add A (high byte of second result) to R BCS MA30 ; If the addition of the two high bytes caused a carry ; (i.e. they overflowed), jump to MA30 to return A = $FF STA R ; Store A (sum of the two high bytes) in R LDA K%+7,Y ; Set (A P) = z_hi * z_hi JSR SQUA2 ADC R ; Add A (high byte of third result) to R, so R now ; contains the high byte of the entire sum, i.e. of ; x_hi^2 + y_hi^2 + z_hi^2 BCC P%+4 ; If there is no carry, skip the following instruction ; to return straight from the subroutine .MA30 LDA #$FF ; The calculation has overflowed, so set A = $FF RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: STATUS ; Type: Subroutine ; Category: Status ; Summary: Show the Status Mode screen ; Deep dive: Combat rank ; ; ****************************************************************************** .wearedocked ; We call this from STATUS below if we are docked LDA #205 ; Print extended token 205 ("DOCKED") and return from JSR DETOK ; the subroutine using a tail call JSR TT67K ; Print a newline JMP st6+3 ; Jump down to st6+3, to print recursive token 125 and ; continue to the rest of the Status Mode screen .st4 ; We call this from st5 below with the high byte of the ; kill tally in A, which is non-zero, and want to return ; with the following in X, depending on our rating: ; ; Competent = 6 ; Dangerous = 7 ; Deadly = 8 ; Elite = 9 ; ; The high bytes of the top tier ratings are as follows, ; so this a relatively simple calculation: ; ; Competent = 1 ; Dangerous = 2 to 9 ; Deadly = 10 to 24 ; Elite = 25 and up LDX #9 ; Set X to 9 for an Elite rating CMP #25 ; If A >= 25, jump to st3 to print out our rating, as we BCS st3 ; are Elite DEX ; Decrement X to 8 for a Deadly rating CMP #10 ; If A >= 10, jump to st3 to print out our rating, as we BCS st3 ; are Deadly DEX ; Decrement X to 7 for a Dangerous rating CMP #2 ; If A >= 2, jump to st3 to print out our rating, as we BCS st3 ; are Dangerous DEX ; Decrement X to 6 for a Competent rating BNE st3 ; Jump to st3 to print out our rating, as we are ; Competent (this BNE is effectively a JMP as A will ; never be zero) .STATUS LDA #8 ; Clear the top part of the screen, draw a border box, JSR TRADEMODE ; and set up a trading screen with a view type in QQ11 ; of 8 (Status Mode screen) JSR TT111 ; Select the system closest to galactic coordinates ; (QQ9, QQ10) LDA #7 ; Move the text cursor to column 7 JSR DOXC LDA #126 ; Print recursive token 126, which prints the top JSR NLIN3 ; four lines of the Status Mode screen: ; ; COMMANDER {commander name} ; ; ; Present System : {current system name} ; Hyperspace System : {selected system name} ; Condition : ; ; and draw a horizontal line at pixel row 19 to box ; in the title LDA #15 ; This instruction is left over from the cassette ; version, where it sets the token number for the ; "DOCKED" text, but it has no effect in this version ; as the "DOCKED" text is now an extended token LDY QQ12 ; Fetch the docked status from QQ12, and if we are BNE wearedocked ; docked, jump to wearedocked LDA #230 ; Otherwise we are in space, so start off by setting A ; to token 70 ("GREEN") LDY JUNK ; Set Y to the number of junk items in our local bubble ; of universe (where junk is asteroids, canisters, ; escape pods and so on) LDX FRIN+2,Y ; The ship slots at FRIN are ordered with the first two ; slots reserved for the planet and sun/space station, ; and then any ships, so if the slot at FRIN+2+Y is not ; empty (i.e. is non-zero), then that means the number ; of non-asteroids in the vicinity is at least 1 BEQ st6 ; So if X = 0, there are no ships in the vicinity, so ; jump to st6 to print "Green" for our ship's condition LDY ENERGY ; Otherwise we have ships in the vicinity, so we load ; our energy levels into Y CPY #128 ; Set the C flag if Y >= 128, so C is set if we have ; more than half of our energy banks charged ADC #1 ; Add 1 + C to A, so if C is not set (i.e. we have low ; energy levels) then A is set to token 231 ("RED"), ; and if C is set (i.e. we have healthy energy levels) ; then A is set to token 232 ("YELLOW") .st6 JSR plf ; Print the text token in A (which contains our ship's ; condition) followed by a newline LDA #125 ; Print recursive token 125, which prints the next JSR spc ; three lines of the Status Mode screen: ; ; Fuel: {fuel level} Light Years ; Cash: {cash} Cr ; Legal Status: ; ; followed by a space LDA #19 ; Set A to token 133 ("CLEAN") LDY FIST ; Fetch our legal status, and if it is 0, we are clean, BEQ st5 ; so jump to st5 to print "Clean" CPY #50 ; Set the C flag if Y >= 50, so C is set if we have ; a legal status of 50+ (i.e. we are a fugitive) ADC #1 ; Add 1 + C to A, so if C is not set (i.e. we have a ; legal status between 1 and 49) then A is set to token ; 134 ("OFFENDER"), and if C is set (i.e. we have a ; legal status of 50+) then A is set to token 135 ; ("FUGITIVE") .st5 JSR plf ; Print the text token in A (which contains our legal ; status) followed by a newline LDA #16 ; Print recursive token 130 ("RATING:") followed by a JSR spc ; space LDA TALLY+1 ; Fetch the high byte of the kill tally, and if it is BNE st4 ; not zero, then we have more than 256 kill points, so ; jump to st4 to work out whether we are Competent, ; Dangerous, Deadly or Elite ; Otherwise we have fewer than 256 kill pointss, so we ; are one of Harmless, Mostly Harmless, Poor, Average, ; Above Average or Competent TAX ; Set X to 0 (as A is 0) LDA TALLY ; Set A to the lower byte of tally, with bits 0 and 1 LSR A ; shifted off to the right, so we can now analyse bits LSR A ; 2 to 7 by shifting A to the right one bit at a time ; We now loop through bits 2 to 7, shifting each of them ; off the end of A until there are no set bits left, and ; incrementing X before each shift, so at the end of the ; process, X contains the position of the leftmost 1 in ; A. Looking at the rank values in TALLY: ; ; Harmless = %00000000 to %00000111 ; Mostly Harmless = %00001000 to %00001111 ; Poor = %00010000 to %00011111 ; Average = %00100000 to %00111111 ; Above Average = %01000000 to %01111111 ; Competent = %10000000 to %11111111 ; ; we can see that the values returned by this process ; are: ; ; Harmless = 1 ; Mostly Harmless = 2 ; Poor = 3 ; Average = 4 ; Above Average = 5 ; Competent = 6 INX ; Increment X to count the number of shifts LSR A ; Shift A to the right BNE P%-2 ; Keep looping back two instructions (i.e. to the INX ; instruction) until A = 0, which means there are no set ; bits left in A .st3 TXA ; A now contains our rating as a value of 1 to 9, so ; transfer X to A, so we can print it out CLC ; Print recursive token 135 + A, which will be in the ADC #21 ; range 136 ("HARMLESS") to 144 ("---- E L I T E ----") JSR plf ; followed by a newline LDA #18 ; Print recursive token 132, which prints the next bit JSR plf2 ; of the Status Mode screen: ; ; EQUIPMENT: ; ; followed by a newline and an indent of 6 characters LDA ESCP ; If we don't have an escape pod fitted (i.e. ESCP is BEQ P%+7 ; zero), skip the following two instructions LDA #112 ; We do have an escape pod fitted, so print recursive JSR plf2 ; token 112 ("ESCAPE POD"), followed by a newline and an ; indent of 6 characters LDA BST ; If we don't have fuel scoops fitted, skip the BEQ P%+7 ; following two instructions LDA #111 ; We do have fuel scoops fitted, so print recursive JSR plf2 ; token 111 ("FUEL SCOOPS"), followed by a newline and ; an indent of 6 characters LDA ECM ; If we don't have an E.C.M. fitted, skip the following BEQ P%+7 ; two instructions LDA #108 ; We do have an E.C.M. fitted, so print recursive token JSR plf2 ; 108 ("E.C.M.SYSTEM"), followed by a newline and an ; indent of 6 characters LDA #113 ; We now cover the four pieces of equipment whose flags STA XX4 ; are stored in BOMB through BOMB+3, and whose names ; correspond with text tokens 113 through 116: ; ; BOMB+0 = BOMB = token 113 = Energy bomb ; BOMB+1 = ENGY = token 114 = Energy unit ; BOMB+2 = DKCMP = token 115 = Docking computer ; BOMB+3 = GHYP = token 116 = Galactic hyperdrive ; ; We can print these out using a loop, so we set XX4 to ; 113 as a counter (and we also set A as well, to pass ; through to plf2) .stqv TAY ; Fetch byte BOMB+0 through BOMB+4 for values of XX4 LDX BOMB-113,Y ; from 113 through 117 BEQ P%+5 ; If it is zero then we do not own that piece of ; equipment, so skip the next instruction JSR plf2 ; Print the recursive token in A from 113 ("ENERGY ; BOMB") through 116 ("GALACTIC HYPERSPACE "), followed ; by a newline and an indent of 6 characters INC XX4 ; Increment the counter (and A as well) LDA XX4 CMP #117 ; If A < 117, loop back up to stqv to print the next BCC stqv ; piece of equipment LDX #0 ; Now to print our ship's lasers, so set a counter in X ; to count through the four views (0 = front, 1 = rear, ; 2 = left, 3 = right) .st STX CNT ; Store the view number in CNT LDY LASER,X ; Fetch the laser power for view X, and if we do not BEQ st1 ; have a laser fitted to that view, jump to st1 to move ; on to the next one TXA ; Print recursive token 96 + X, which will print from 96 CLC ; ("FRONT") through to 99 ("RIGHT"), followed by a space ADC #96 JSR spc LDA #103 ; Set A to token 103 ("PULSE LASER") LDX CNT ; Retrieve the view number from CNT that we stored above LDY LASER,X ; Set Y = the laser power for view X CPY #128+POW ; If the laser power for view X is not #POW+128 (beam BNE P%+4 ; laser), skip the next LDA instruction LDA #104 ; This sets A = 104 if the laser in view X is a beam ; laser (token 104 is "BEAM LASER") CPY #Armlas ; If the laser power for view X is not #Armlas (military BNE P%+4 ; laser), skip the next LDA instruction LDA #117 ; This sets A = 117 if the laser in view X is a military ; laser (token 117 is "MILITARY LASER") CPY #Mlas ; If the laser power for view X is not #Mlas (mining BNE P%+4 ; laser), skip the next LDA instruction LDA #118 ; This sets A = 118 if the laser in view X is a mining ; laser (token 118 is "MINING LASER") JSR plf2 ; Print the text token in A (which contains the laser ; type) followed by a newline and an indent of 6 ; characters .st1 LDX CNT ; Increment the counter in X and CNT to point to the INX ; next view CPX #4 ; If this isn't the last of the four views, jump back up BCC st ; to st to print out the next one RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: plf2 ; Type: Subroutine ; Category: Text ; Summary: Print text followed by a newline and indent of 6 characters ; ; ------------------------------------------------------------------------------ ; ; Print a text token followed by a newline, and indent the next line to text ; column 6. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text token to be printed ; ; ****************************************************************************** .plf2 JSR plf ; Print the text token in A followed by a newline LDA #6 ; Move the text cursor to column 6 and return from the JMP DOXC ; subroutine using a tail call ; ****************************************************************************** ; ; Name: MVT3 ; Type: Subroutine ; Category: Moving ; Summary: Calculate K(3 2 1) = (x_sign x_hi x_lo) + K(3 2 1) ; ; ------------------------------------------------------------------------------ ; ; Add an INWK position coordinate - i.e. x, y or z - to K(3 2 1), like this: ; ; K(3 2 1) = (x_sign x_hi x_lo) + K(3 2 1) ; ; The INWK coordinate to add to K(3 2 1) is specified by X. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The coordinate to add to K(3 2 1), as follows: ; ; * If X = 0, add (x_sign x_hi x_lo) ; ; * If X = 3, add (y_sign y_hi y_lo) ; ; * If X = 6, add (z_sign z_hi z_lo) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A Contains a copy of the high byte of the result, K+3 ; ; X X is preserved ; ; ****************************************************************************** .MVT3 LDA K+3 ; Set S = K+3 STA S AND #%10000000 ; Set T = sign bit of K(3 2 1) STA T EOR INWK+2,X ; If x_sign has a different sign to K(3 2 1), jump to BMI MV13 ; MV13 to process the addition as a subtraction LDA K+1 ; Set K(3 2 1) = K(3 2 1) + (x_sign x_hi x_lo) CLC ; starting with the low bytes ADC INWK,X STA K+1 LDA K+2 ; Then the middle bytes ADC INWK+1,X STA K+2 LDA K+3 ; And finally the high bytes ADC INWK+2,X AND #%01111111 ; Setting the sign bit of K+3 to T, the original sign ORA T ; of K(3 2 1) STA K+3 RTS ; Return from the subroutine .MV13 LDA S ; Set S = |K+3| (i.e. K+3 with the sign bit cleared) AND #%01111111 STA S LDA INWK,X ; Set K(3 2 1) = (x_sign x_hi x_lo) - K(3 2 1) SEC ; starting with the low bytes SBC K+1 STA K+1 LDA INWK+1,X ; Then the middle bytes SBC K+2 STA K+2 LDA INWK+2,X ; And finally the high bytes, doing A = |x_sign| - |K+3| AND #%01111111 ; and setting the C flag for testing below SBC S ORA #%10000000 ; Set the sign bit of K+3 to the opposite sign of T, EOR T ; i.e. the opposite sign to the original K(3 2 1) STA K+3 BCS MV14 ; If the C flag is set, i.e. |x_sign| >= |K+3|, then ; the sign of K(3 2 1). In this case, we want the ; result to have the same sign as the largest argument, ; which is (x_sign x_hi x_lo), which we know has the ; opposite sign to K(3 2 1), and that's what we just set ; the sign of K(3 2 1) to... so we can jump to MV14 to ; return from the subroutine LDA #1 ; We need to swap the sign of the result in K(3 2 1), SBC K+1 ; which we do by calculating 0 - K(3 2 1), which we can STA K+1 ; do with 1 - C - K(3 2 1), as we know the C flag is ; clear. We start with the low bytes LDA #0 ; Then the middle bytes SBC K+2 STA K+2 LDA #0 ; And finally the high bytes SBC K+3 AND #%01111111 ; Set the sign bit of K+3 to the same sign as T, ORA T ; i.e. the same sign as the original K(3 2 1), as STA K+3 ; that's the largest argument .MV14 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MVS5 ; Type: Subroutine ; Category: Moving ; Summary: Apply a 3.6 degree pitch or roll to an orientation vector ; Deep dive: Orientation vectors ; Pitching and rolling by a fixed angle ; ; ------------------------------------------------------------------------------ ; ; Pitch or roll a ship by a small, fixed amount (1/16 radians, or 3.6 degrees), ; in a specified direction, by rotating the orientation vectors. The vectors to ; rotate are given in X and Y, and the direction of the rotation is given in ; RAT2. The calculation is as follows: ; ; * If the direction is positive: ; ; X = X * (1 - 1/512) + Y / 16 ; Y = Y * (1 - 1/512) - X / 16 ; ; * If the direction is negative: ; ; X = X * (1 - 1/512) - Y / 16 ; Y = Y * (1 - 1/512) + X / 16 ; ; So if X = 15 (roofv_x), Y = 21 (sidev_x) and RAT2 is positive, it does this: ; ; roofv_x = roofv_x * (1 - 1/512) + sidev_x / 16 ; sidev_x = sidev_x * (1 - 1/512) - roofv_x / 16 ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The first vector to rotate: ; ; * If X = 15, rotate roofv_x ; ; * If X = 17, rotate roofv_y ; ; * If X = 19, rotate roofv_z ; ; * If X = 21, rotate sidev_x ; ; * If X = 23, rotate sidev_y ; ; * If X = 25, rotate sidev_z ; ; Y The second vector to rotate: ; ; * If Y = 9, rotate nosev_x ; ; * If Y = 11, rotate nosev_y ; ; * If Y = 13, rotate nosev_z ; ; * If Y = 21, rotate sidev_x ; ; * If Y = 23, rotate sidev_y ; ; * If Y = 25, rotate sidev_z ; ; RAT2 The direction of the pitch or roll to perform, positive ; or negative (i.e. the sign of the roll or pitch counter ; in bit 7) ; ; ****************************************************************************** .MVS5 LDA INWK+1,X ; Fetch roofv_x_hi, clear the sign bit, divide by 2 and AND #%01111111 ; store in T, so: LSR A ; STA T ; T = |roofv_x_hi| / 2 ; = |roofv_x| / 512 ; ; The above is true because: ; ; |roofv_x| = |roofv_x_hi| * 256 + roofv_x_lo ; ; so: ; ; |roofv_x| / 512 = |roofv_x_hi| * 256 / 512 ; + roofv_x_lo / 512 ; = |roofv_x_hi| / 2 LDA INWK,X ; Now we do the following subtraction: SEC ; SBC T ; (S R) = (roofv_x_hi roofv_x_lo) - |roofv_x| / 512 STA R ; = (1 - 1/512) * roofv_x ; ; by doing the low bytes first LDA INWK+1,X ; And then the high bytes (the high byte of the right SBC #0 ; side of the subtraction being 0) STA S LDA INWK,Y ; Set P = nosev_x_lo STA P LDA INWK+1,Y ; Fetch the sign of nosev_x_hi (bit 7) and store in T AND #%10000000 STA T LDA INWK+1,Y ; Fetch nosev_x_hi into A and clear the sign bit, so AND #%01111111 ; A = |nosev_x_hi| LSR A ; Set (A P) = (A P) / 16 ROR P ; = |nosev_x_hi nosev_x_lo| / 16 LSR A ; = |nosev_x| / 16 ROR P LSR A ROR P LSR A ROR P ORA T ; Set the sign of A to the sign in T (i.e. the sign of ; the original nosev_x), so now: ; ; (A P) = nosev_x / 16 EOR RAT2 ; Give it the sign as if we multiplied by the direction ; by the pitch or roll direction STX Q ; Store the value of X so it can be restored after the ; call to ADD JSR ADD ; (A X) = (A P) + (S R) ; = +/-nosev_x / 16 + (1 - 1/512) * roofv_x STA K+1 ; Set K(1 0) = (1 - 1/512) * roofv_x +/- nosev_x / 16 STX K LDX Q ; Restore the value of X from before the call to ADD LDA INWK+1,Y ; Fetch nosev_x_hi, clear the sign bit, divide by 2 and AND #%01111111 ; store in T, so: LSR A ; STA T ; T = |nosev_x_hi| / 2 ; = |nosev_x| / 512 LDA INWK,Y ; Now we do the following subtraction: SEC ; SBC T ; (S R) = (nosev_x_hi nosev_x_lo) - |nosev_x| / 512 STA R ; = (1 - 1/512) * nosev_x ; ; by doing the low bytes first LDA INWK+1,Y ; And then the high bytes (the high byte of the right SBC #0 ; side of the subtraction being 0) STA S LDA INWK,X ; Set P = roofv_x_lo STA P LDA INWK+1,X ; Fetch the sign of roofv_x_hi (bit 7) and store in T AND #%10000000 STA T LDA INWK+1,X ; Fetch roofv_x_hi into A and clear the sign bit, so AND #%01111111 ; A = |roofv_x_hi| LSR A ; Set (A P) = (A P) / 16 ROR P ; = |roofv_x_hi roofv_x_lo| / 16 LSR A ; = |roofv_x| / 16 ROR P LSR A ROR P LSR A ROR P ORA T ; Set the sign of A to the opposite sign to T (i.e. the EOR #%10000000 ; sign of the original -roofv_x), so now: ; ; (A P) = -roofv_x / 16 EOR RAT2 ; Give it the sign as if we multiplied by the direction ; by the pitch or roll direction STX Q ; Store the value of X so it can be restored after the ; call to ADD JSR ADD ; (A X) = (A P) + (S R) ; = -/+roofv_x / 16 + (1 - 1/512) * nosev_x STA INWK+1,Y ; Set nosev_x = (1-1/512) * nosev_x -/+ roofv_x / 16 STX INWK,Y LDX Q ; Restore the value of X from before the call to ADD LDA K ; Set roofv_x = K(1 0) STA INWK,X ; = (1-1/512) * roofv_x +/- nosev_x / 16 LDA K+1 STA INWK+1,X RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TENS ; Type: Variable ; Category: Text ; Summary: A constant used when printing large numbers in BPRNT ; Deep dive: Printing decimal numbers ; ; ------------------------------------------------------------------------------ ; ; Contains the four low bytes of the value 100,000,000,000 (100 billion). ; ; The maximum number of digits that we can print with the BPRNT routine is 11, ; so the biggest number we can print is 99,999,999,999. This maximum number ; plus 1 is 100,000,000,000, which in hexadecimal is: ; ; 17 48 76 E8 00 ; ; The TENS variable contains the lowest four bytes in this number, with the ; most significant byte first, i.e. 48 76 E8 00. This value is used in the ; BPRNT routine when working out which decimal digits to print when printing a ; number. ; ; ****************************************************************************** .TENS EQUD $00E87648 ; ****************************************************************************** ; ; Name: pr2 ; Type: Subroutine ; Category: Text ; Summary: Print an 8-bit number, left-padded to 3 digits, and optional point ; ; ------------------------------------------------------------------------------ ; ; Print the 8-bit number in X to 3 digits, left-padding with spaces for numbers ; with fewer than 3 digits (so numbers < 100 are right-aligned). Optionally ; include a decimal point. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The number to print ; ; C flag If set, include a decimal point ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; pr2+2 Print the 8-bit number in X to the number of digits in A ; ; ****************************************************************************** .pr2 LDA #3 ; Set A to the number of digits (3) LDY #0 ; Zero the Y register, so we can fall through into TT11 ; to print the 16-bit number (Y X) to 3 digits, which ; effectively prints X to 3 digits as the high byte is ; zero ; ****************************************************************************** ; ; Name: TT11 ; Type: Subroutine ; Category: Text ; Summary: Print a 16-bit number, left-padded to n digits, and optional point ; ; ------------------------------------------------------------------------------ ; ; Print the 16-bit number in (Y X) to a specific number of digits, left-padding ; with spaces for numbers with fewer digits (so lower numbers will be right- ; aligned). Optionally include a decimal point. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The low byte of the number to print ; ; Y The high byte of the number to print ; ; A The number of digits ; ; C flag If set, include a decimal point ; ; ****************************************************************************** .TT11 STA U ; We are going to use the BPRNT routine (below) to ; print this number, so we store the number of digits ; in U, as that's what BPRNT takes as an argument LDA #0 ; BPRNT takes a 32-bit number in K to K+3, with the STA K ; most significant byte first (big-endian), so we set STA K+1 ; the two most significant bytes to zero (K and K+1) STY K+2 ; and store (Y X) in the least two significant bytes STX K+3 ; (K+2 and K+3), so we are going to print the 32-bit ; number (0 0 Y X) ; Finally we fall through into BPRNT to print out the ; number in K to K+3, which now contains (Y X), to A ; digits (as U = A), using the same C flag as when pr2 ; was called to control the decimal point ; ****************************************************************************** ; ; Name: BPRNT ; Type: Subroutine ; Category: Text ; Summary: Print a 32-bit number, left-padded to a specific number of digits, ; with an optional decimal point ; Deep dive: Printing decimal numbers ; ; ------------------------------------------------------------------------------ ; ; Print the 32-bit number stored in K(0 1 2 3) to a specific number of digits, ; left-padding with spaces for numbers with fewer digits (so lower numbers are ; right-aligned). Optionally include a decimal point. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; K(0 1 2 3) The number to print, stored with the most significant ; byte in K and the least significant in K+3 (i.e. as a ; big-endian number, which is the opposite way to how the ; 6502 assembler stores addresses, for example) ; ; U The maximum number of digits to print, including the ; decimal point (spaces will be used on the left to pad ; out the result to this width, so the number is right- ; aligned to this width). U must be 11 or less ; ; C flag If set, include a decimal point followed by one ; fractional digit (i.e. show the number to 1 decimal ; place). In this case, the number in K(0 1 2 3) contains ; 10 * the number we end up printing, so to print 123.4, ; we would pass 1234 in K(0 1 2 3) and would set the C ; flag to include the decimal point ; ; ****************************************************************************** .BPRNT LDX #11 ; Set T to the maximum number of digits allowed (11 STX T ; characters, which is the number of digits in 10 ; billion). We will use this as a flag when printing ; characters in TT37 below PHP ; Make a copy of the status register (in particular ; the C flag) so we can retrieve it later BCC TT30 ; If the C flag is clear, we do not want to print a ; decimal point, so skip the next two instructions DEC T ; As we are going to show a decimal point, decrement DEC U ; both the number of characters and the number of ; digits (as one of them is now a decimal point) .TT30 LDA #11 ; Set A to 11, the maximum number of digits allowed SEC ; Set the C flag so we can do subtraction without the ; C flag affecting the result STA XX17 ; Store the maximum number of digits allowed (11) in ; XX17 SBC U ; Set U = 11 - U + 1, so U now contains the maximum STA U ; number of digits minus the number of digits we want INC U ; to display, plus 1 (so this is the number of digits ; we should skip before starting to print the number ; itself, and the plus 1 is there to ensure we print at ; least one digit) LDY #0 ; In the main loop below, we use Y to count the number ; of times we subtract 10 billion to get the leftmost ; digit, so set this to zero STY S ; In the main loop below, we use location S as an ; 8-bit overflow for the 32-bit calculations, so ; we need to set this to 0 before joining the loop JMP TT36 ; Jump to TT36 to start the process of printing this ; number's digits .TT35 ; This subroutine multiplies K(S 0 1 2 3) by 10 and ; stores the result back in K(S 0 1 2 3), using the fact ; that K * 10 = (K * 2) + (K * 2 * 2 * 2) ASL K+3 ; Set K(S 0 1 2 3) = K(S 0 1 2 3) * 2 by rotating left ROL K+2 ROL K+1 ROL K ROL S LDX #3 ; Now we want to make a copy of the newly doubled K in ; XX15, so we can use it for the first (K * 2) in the ; equation above, so set up a counter in X for copying ; four bytes, starting with the last byte in memory ; (i.e. the least significant) .tt35 LDA K,X ; Copy the X-th byte of K(0 1 2 3) to the X-th byte of STA XX15,X ; XX15(0 1 2 3), so that XX15 will contain a copy of ; K(0 1 2 3) once we've copied all four bytes DEX ; Decrement the loop counter BPL tt35 ; Loop back to copy the next byte until we have copied ; all four LDA S ; Store the value of location S, our overflow byte, in STA XX15+4 ; XX15+4, so now XX15(4 0 1 2 3) contains a copy of ; K(S 0 1 2 3), which is the value of (K * 2) that we ; want to use in our calculation ASL K+3 ; Now to calculate the (K * 2 * 2 * 2) part. We still ROL K+2 ; have (K * 2) in K(S 0 1 2 3), so we just need to shift ROL K+1 ; it twice. This is the first one, so we do this: ROL K ; ROL S ; K(S 0 1 2 3) = K(S 0 1 2 3) * 2 = K * 4 ASL K+3 ; And then we do it again, so that means: ROL K+2 ; ROL K+1 ; K(S 0 1 2 3) = K(S 0 1 2 3) * 2 = K * 8 ROL K ROL S CLC ; Clear the C flag so we can do addition without the ; C flag affecting the result LDX #3 ; By now we've got (K * 2) in XX15(4 0 1 2 3) and ; (K * 8) in K(S 0 1 2 3), so the final step is to add ; these two 32-bit numbers together to get K * 10. ; So we set a counter in X for four bytes, starting ; with the last byte in memory (i.e. the least ; significant) .tt36 LDA K,X ; Fetch the X-th byte of K into A ADC XX15,X ; Add the X-th byte of XX15 to A, with carry STA K,X ; Store the result in the X-th byte of K DEX ; Decrement the loop counter BPL tt36 ; Loop back to add the next byte, moving from the least ; significant byte to the most significant, until we ; have added all four LDA XX15+4 ; Finally, fetch the overflow byte from XX15(4 0 1 2 3) ADC S ; And add it to the overflow byte from K(S 0 1 2 3), ; with carry STA S ; And store the result in the overflow byte from ; K(S 0 1 2 3), so now we have our desired result, i.e. ; ; K(S 0 1 2 3) = K(S 0 1 2 3) * 10 LDY #0 ; In the main loop below, we use Y to count the number ; of times we subtract 10 billion to get the leftmost ; digit, so set this to zero so we can rejoin the main ; loop for another subtraction process .TT36 ; This is the main loop of our digit-printing routine. ; In the following loop, we are going to count the ; number of times that we can subtract 10 million and ; store that count in Y, which we have already set to 0 LDX #3 ; Our first calculation concerns 32-bit numbers, so ; set up a counter for a four-byte loop SEC ; Set the C flag so we can do subtraction without the ; C flag affecting the result .tt37 ; We now loop through each byte in turn to do this: ; ; XX15(4 0 1 2 3) = K(S 0 1 2 3) - 100,000,000,000 LDA K,X ; Subtract the X-th byte of TENS (i.e. 10 billion) from SBC TENS,X ; the X-th byte of K STA XX15,X ; Store the result in the X-th byte of XX15 DEX ; Decrement the loop counter BPL tt37 ; Loop back to subtract the next byte, moving from the ; least significant byte to the most significant, until ; we have subtracted all four LDA S ; Subtract the fifth byte of 10 billion (i.e. $17) from SBC #$17 ; the fifth (overflow) byte of K, which is S STA XX15+4 ; Store the result in the overflow byte of XX15 BCC TT37 ; If subtracting 10 billion took us below zero, jump to ; TT37 to print out this digit, which is now in Y LDX #3 ; We now want to copy XX15(4 0 1 2 3) back into ; K(S 0 1 2 3), so we can loop back up to do the next ; subtraction, so set up a counter for a four-byte loop .tt38 LDA XX15,X ; Copy the X-th byte of XX15(0 1 2 3) to the X-th byte STA K,X ; of K(0 1 2 3), so that K(0 1 2 3) will contain a copy ; of XX15(0 1 2 3) once we've copied all four bytes DEX ; Decrement the loop counter BPL tt38 ; Loop back to copy the next byte, until we have copied ; all four LDA XX15+4 ; Store the value of location XX15+4, our overflow STA S ; byte in S, so now K(S 0 1 2 3) contains a copy of ; XX15(4 0 1 2 3) INY ; We have now managed to subtract 10 billion from our ; number, so increment Y, which is where we are keeping ; a count of the number of subtractions so far JMP TT36 ; Jump back to TT36 to subtract the next 10 billion .TT37 TYA ; If we get here then Y contains the digit that we want ; to print (as Y has now counted the total number of ; subtractions of 10 billion), so transfer Y into A BNE TT32 ; If the digit is non-zero, jump to TT32 to print it LDA T ; Otherwise the digit is zero. If we are already ; printing the number then we will want to print a 0, ; but if we haven't started printing the number yet, ; then we probably don't, as we don't want to print ; leading zeroes unless this is the only digit before ; the decimal point ; ; To help with this, we are going to use T as a flag ; that tells us whether we have already started ; printing digits: ; ; * If T <> 0 we haven't printed anything yet ; ; * If T = 0 then we have started printing digits ; ; We initially set T above to the maximum number of ; characters allowed, less 1 if we are printing a ; decimal point, so the first time we enter the digit ; printing routine at TT37, it is definitely non-zero BEQ TT32 ; If T = 0, jump straight to the print routine at TT32, ; as we have already started printing the number, so we ; definitely want to print this digit too DEC U ; We initially set U to the number of digits we want to BPL TT34 ; skip before starting to print the number. If we get ; here then we haven't printed any digits yet, so ; decrement U to see if we have reached the point where ; we should start printing the number, and if not, jump ; to TT34 to set up things for the next digit LDA #' ' ; We haven't started printing any digits yet, but we BNE tt34 ; have reached the point where we should start printing ; our number, so call TT26 (via tt34) to print a space ; so that the number is left-padded with spaces (this ; BNE is effectively a JMP as A will never be zero) .TT32 LDY #0 ; We are printing an actual digit, so first set T to 0, STY T ; to denote that we have now started printing digits as ; opposed to spaces CLC ; The digit value is in A, so add ASCII "0" to get the ADC #'0' ; ASCII character number to print .tt34 JSR TT26 ; Call TT26 to print the character in A and fall through ; into TT34 to get things ready for the next digit .TT34 DEC T ; Decrement T but keep T >= 0 (by incrementing it BPL P%+4 ; again if the above decrement made T negative) INC T DEC XX17 ; Decrement the total number of characters left to ; print, which we stored in XX17 BMI rT10 ; If the result is negative, we have printed all the ; characters, so jump down to rT10 to return from the ; subroutine BNE P%+10 ; If the result is positive (> 0) then we still have ; characters left to print, so loop back to TT35 (via ; the JMP TT35 instruction below) to print the next ; digit PLP ; If we get here then we have printed the exact number ; of digits that we wanted to, so restore the C flag ; that we stored at the start of the routine BCC P%+7 ; If the C flag is clear, we don't want a decimal point, ; so loop back to TT35 (via the JMP TT35 instruction ; below) to print the next digit LDA #'.' ; Otherwise the C flag is set, so print the decimal JSR TT26 ; point JMP TT35 ; Loop back to TT35 to print the next digit .rT10 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DTW1 ; Type: Variable ; Category: Text ; Summary: A mask for applying the lower case part of Sentence Case to ; extended text tokens ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This variable is used to change characters to lower case as part of applying ; Sentence Case to extended text tokens. It has two values: ; ; * %00100000 = apply lower case to the second letter of a word onwards ; ; * %00000000 = do not change case to lower case ; ; The default value is %00100000 (apply lower case). ; ; The flag is set to %00100000 (apply lower case) by jump token 2, {sentence ; case}, which calls routine MT2 to change the value of DTW1. ; ; The flag is set to %00000000 (do not change case to lower case) by jump token ; 1, {all caps}, which calls routine MT1 to change the value of DTW1. ; ; The letter to print is OR'd with DTW1 in DETOK2, which lower-cases the letter ; by setting bit 5 (if DTW1 is %00100000). However, this OR is only done if bit ; 7 of DTW2 is clear, i.e. we are printing a word, so this doesn't affect the ; first letter of the word, which remains capitalised. ; ; ****************************************************************************** .DTW1 EQUB %00100000 ; ****************************************************************************** ; ; Name: DTW2 ; Type: Variable ; Category: Text ; Summary: A flag that indicates whether we are currently printing a word ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This variable is used to indicate whether we are currently printing a word. It ; has two values: ; ; * 0 = we are currently printing a word ; ; * Non-zero = we are not currently printing a word ; ; The default value is %11111111 (we are not currently printing a word). ; ; The flag is set to %00000000 (we are currently printing a word) whenever a ; non-terminator character is passed to DASC for printing. ; ; The flag is set to %11111111 (we are not currently printing a word) whenever a ; terminator character (full stop, colon, carriage return, line feed, space) is ; passed to DASC for printing. It is also set to %11111111 by jump token 8, ; {tab 6}, which calls routine MT8 to change the value of DTW2, and to %10000000 ; by TTX66 when we clear the screen. ; ; ****************************************************************************** .DTW2 EQUB %11111111 ; ****************************************************************************** ; ; Name: DTW3 ; Type: Variable ; Category: Text ; Summary: A flag for switching between standard and extended text tokens ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This variable is used to indicate whether standard or extended text tokens ; should be printed by calls to DETOK. It allows us to mix standard tokens in ; with extended tokens. It has two values: ; ; * %00000000 = print extended tokens (i.e. those in TKN1 and RUTOK) ; ; * %11111111 = print standard tokens (i.e. those in QQ18) ; ; The default value is %00000000 (extended tokens). ; ; Standard tokens are set by jump token {6}, which calls routine MT6 to change ; the value of DTW3 to %11111111. ; ; Extended tokens are set by jump token {5}, which calls routine MT5 to change ; the value of DTW3 to %00000000. ; ; ****************************************************************************** .DTW3 EQUB %00000000 ; ****************************************************************************** ; ; Name: DTW4 ; Type: Variable ; Category: Text ; Summary: Flags that govern how justified extended text tokens are printed ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This variable is used to control how justified text tokens are printed as part ; of the extended text token system. There are two bits that affect justified ; text: ; ; * Bit 7: 1 = justify text ; 0 = do not justify text ; ; * Bit 6: 1 = buffer the entire token before printing, including carriage ; returns (used for in-flight messages only) ; 0 = print the contents of the buffer whenever a carriage return ; appears in the token ; ; The default value is %00000000 (do not justify text, print buffer on carriage ; return). ; ; The flag is set to %10000000 (justify text, print buffer on carriage return) ; by jump token 14, {justify}, which calls routine MT14 to change the value of ; DTW4. ; ; The flag is set to %11000000 (justify text, buffer entire token) by routine ; MESS, which prints in-flight messages. ; ; The flag is set to %00000000 (do not justify text, print buffer on carriage ; return) by jump token 15, {left align}, which calls routine MT1 to change the ; value of DTW4. ; ; ****************************************************************************** .DTW4 EQUB 0 ; ****************************************************************************** ; ; Name: DTW5 ; Type: Variable ; Category: Text ; Summary: The size of the justified text buffer at BUF ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; When justified text is enabled by jump token 14, {justify}, during printing of ; extended text tokens, text is fed into a buffer at BUF instead of being ; printed straight away, so it can be padded out with spaces to justify the ; text. DTW5 contains the size of the buffer, so BUF + DTW5 points to the first ; free byte after the end of the buffer. ; ; ****************************************************************************** .DTW5 EQUB 0 ; ****************************************************************************** ; ; Name: DTW6 ; Type: Variable ; Category: Text ; Summary: A flag to denote whether printing in lower case is enabled for ; extended text tokens ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This variable is used to indicate whether lower case is currently enabled. It ; has two values: ; ; * %10000000 = lower case is enabled ; ; * %00000000 = lower case is not enabled ; ; The default value is %00000000 (lower case is not enabled). ; ; The flag is set to %10000000 (lower case is enabled) by jump token 13 {lower ; case}, which calls routine MT10 to change the value of DTW6. ; ; The flag is set to %00000000 (lower case is not enabled) by jump token 1, {all ; caps}, and jump token 2, {sentence case}, which call routines MT1 and MT2 to ; change the value of DTW6. ; ; ****************************************************************************** .DTW6 EQUB %00000000 ; ****************************************************************************** ; ; Name: DTW8 ; Type: Variable ; Category: Text ; Summary: A mask for capitalising the next letter in an extended text token ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This variable is only used by one specific extended token, the {single cap} ; jump token, which capitalises the next letter only. It has two values: ; ; * %11011111 = capitalise the next letter ; ; * %11111111 = do not change case ; ; The default value is %11111111 (do not change case). ; ; The flag is set to %11011111 (capitalise the next letter) by jump token 19, ; {single cap}, which calls routine MT19 to change the value of DTW. ; ; The flag is set to %11111111 (do not change case) at the start of DASC, after ; the letter has been capitalised in DETOK2, so the effect is to capitalise one ; letter only. ; ; The letter to print is AND'd with DTW8 in DETOK2, which capitalises the letter ; by clearing bit 5 (if DTW8 is %11011111). However, this AND is only done if at ; least one of the following is true: ; ; * Bit 7 of DTW2 is set (we are not currently printing a word) ; ; * Bit 7 of DTW6 is set (lower case has been enabled by jump token 13, {lower ; case} ; ; In other words, we only capitalise the next letter if it's the first letter in ; a word, or we are printing in lower case. ; ; ****************************************************************************** .DTW8 EQUB %11111111 ; ****************************************************************************** ; ; Name: FEED ; Type: Subroutine ; Category: Text ; Summary: Print a newline ; ; ****************************************************************************** .FEED LDA #12 ; Set A = 12, so when we skip MT16 and fall through into ; TT26, we print character 12, which is a newline EQUB $2C ; Skip the next instruction by turning it into ; $2C $A9 $41, or BIT $41A9, which does nothing apart ; from affect the flags ; Fall through into TT26 (skipping MT16) to print the ; newline character ; ****************************************************************************** ; ; Name: MT16 ; Type: Subroutine ; Category: Text ; Summary: Print the character in variable DTW7 ; Deep dive: Extended text tokens ; ; ****************************************************************************** .MT16 LDA #'A' ; Set A to the contents of DTW7, as DTW7 points to the ; second byte of this instruction, so updating DTW7 will ; modify this instruction (the default value of DTW7 is ; an "A") DTW7 = MT16 + 1 ; Point DTW7 to the second byte of the instruction above ; so that modifying DTW7 changes the value loaded into A ; Fall through into TT26 to print the character in A ; ****************************************************************************** ; ; Name: TT26 ; Type: Subroutine ; Category: Text ; Summary: Print a character at the text cursor, with support for verified ; text in extended tokens ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The character to print ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; X X is preserved ; ; C flag The C flag is cleared ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; DASC DASC does exactly the same as TT26 and prints a ; character at the text cursor, with support for verified ; text in extended tokens ; ; dec27 Contains an RTS ; ; ****************************************************************************** .DASC .TT26 STX SC ; Store X in SC, so we can retrieve it below LDX #%11111111 ; Set DTW8 = %11111111, to disable the effect of {19} if STX DTW8 ; it was set (as {19} capitalises one character only) CMP #'.' ; If the character in A is a word terminator: BEQ DA8 ; CMP #':' ; * Full stop BEQ DA8 ; * Colon CMP #10 ; * Line feed BEQ DA8 ; * Carriage return CMP #12 ; * Space BEQ DA8 ; CMP #' ' ; then skip the following instruction BEQ DA8 INX ; Increment X to 0, so DTW2 gets set to %00000000 below .DA8 STX DTW2 ; Store X in DTW2, so DTW2 is now: ; ; * %00000000 if this character is a word terminator ; ; * %11111111 if it isn't ; ; so DTW2 indicates whether or not we are currently ; printing a word LDX SC ; Retrieve the original value of X from SC BIT DTW4 ; If bit 7 of DTW4 is set then we are currently printing BMI P%+5 ; justified text, so skip the next instruction JMP CHPR ; Bit 7 of DTW4 is clear, so jump down to CHPR to print ; this character, as we are not printing justified text ; If we get here then we are printing justified text, so ; we need to buffer the text until we reach the end of ; the paragraph, so we can then pad it out with spaces BIT DTW4 ; If bit 6 of DTW4 is set, then this is an in-flight BVS P%+6 ; message and we should buffer the carriage return ; character {12}, so skip the following two instructions CMP #12 ; If the character in A is a carriage return, then we BEQ DA1 ; have reached the end of the paragraph, so jump down to ; DA1 to print out the contents of the buffer, ; justifying it as we go ; If we get here then we need to buffer this character ; in the line buffer at BUF LDX DTW5 ; DTW5 contains the current size of the buffer, so this STA BUF,X ; stores the character in A at BUF + DTW5, the next free ; space in the buffer LDX SC ; Retrieve the original value of X from SC so we can ; preserve it through this subroutine call INC DTW5 ; Increment the size of the BUF buffer that is stored in ; DTW5 CLC ; Clear the C flag RTS ; Return from the subroutine .DA1 ; If we get here then we are justifying text and we have ; reached the end of the paragraph, so we need to print ; out the contents of the buffer, justifying it as we go TXA ; Store X and Y on the stack PHA TYA PHA .DA5 LDX DTW5 ; Set X = DTW5, which contains the size of the buffer BEQ DA6+3 ; If X = 0 then the buffer is empty, so jump down to ; DA6+3 to print a newline CPX #(LL+1) ; If X < LL+1, i.e. X <= LL, then the buffer contains BCC DA6 ; fewer than LL characters, which is less than a line ; length, so jump down to DA6 to print the contents of ; BUF followed by a newline, as we don't justify the ; last line of the paragraph ; Otherwise X > LL, so the buffer does not fit into one ; line, and we therefore need to justify the text, which ; we do one line at a time LSR SC+1 ; Shift SC+1 to the right, which clears bit 7 of SC+1, ; so we pass through the following comparison on the ; first iteration of the loop and set SC+1 to %01000000 .DA11 LDA SC+1 ; If bit 7 of SC+1 is set, skip the following two BMI P%+6 ; instructions LDA #%01000000 ; Set SC+1 = %01000000 STA SC+1 LDY #(LL-1) ; Set Y = line length, so we can loop backwards from the ; end of the first line in the buffer using Y as the ; loop counter .DAL1 LDA BUF+LL ; If the LL-th byte in BUF is a space, jump down to DA2 CMP #' ' ; to print out the first line from the buffer, as it BEQ DA2 ; fits the line width exactly (i.e. it's justified) ; We now want to find the last space character in the ; first line in the buffer, so we loop through the line ; using Y as a counter .DAL2 DEY ; Decrement the loop counter in Y BMI DA11 ; If Y <= 0, loop back to DA11, as we have now looped BEQ DA11 ; through the whole line LDA BUF,Y ; If the Y-th byte in BUF is not a space, loop back up CMP #' ' ; to DAL2 to check the next character BNE DAL2 ; Y now points to a space character in the line buffer ASL SC+1 ; Shift SC+1 to the left BMI DAL2 ; If bit 7 of SC+1 is set, jump to DAL2 to find the next ; space character ; We now want to insert a space into the line buffer at ; position Y, which we do by shifting every character ; after position Y along by 1, and then inserting the ; space STY SC ; Store Y in SC, so we want to insert the space at ; position SC LDY DTW5 ; Fetch the buffer size from DTW5 into Y, to act as a ; loop counter for moving the line buffer along by 1 .DAL6 LDA BUF,Y ; Copy the Y-th character from BUF into the Y+1-th STA BUF+1,Y ; position DEY ; Decrement the loop counter in Y CPY SC ; Loop back to shift the next character along, until we BCS DAL6 ; have moved the SC-th character (i.e. Y < SC) INC DTW5 ; Increment the buffer size in DTW5 ;LDA #' ' ; This instruction is commented out in the original ; source, as it has no effect because A already contains ; ASCII " ". This is because the last character that is ; tested in the above loop is at position SC, which we ; know contains a space, so we know A contains a space ; character when the loop finishes ; We've now shifted the line to the right by 1 from ; position SC onwards, so SC and SC+1 both contain ; spaces, and Y is now SC-1 as we did a DEY just before ; the end of the loop - in other words, we have inserted ; a space at position SC, and Y points to the character ; before the newly inserted space ; We now want to move the pointer Y left to find the ; next space in the line buffer, before looping back to ; check whether we are done, and if not, insert another ; space .DAL3 CMP BUF,Y ; If the character at position Y is not a space, jump to BNE DAL1 ; DAL1 to see whether we have now justified the line DEY ; Decrement the loop counter in Y BPL DAL3 ; Loop back to check the next character to the left, ; until we have found a space BMI DA11 ; Jump back to DA11 (this BMI is effectively a JMP as ; we already passed through a BPL to get here) .DA2 ; This subroutine prints out a full line of characters ; from the start of the line buffer in BUF, followed by ; a newline. It then removes that line from the buffer, ; shuffling the rest of the buffer contents down LDX #LL ; Call DAS1 to print out the first LL characters from JSR DAS1 ; the line buffer in BUF LDA #12 ; Print a newline JSR CHPR LDA DTW5 ; Subtract #LL from the end-of-buffer pointer in DTW5 ;CLC ; SBC #LL ; The CLC instruction is commented out in the original STA DTW5 ; source. It isn't needed as CHPR clears the C flag TAX ; Copy the new value of DTW5 into X BEQ DA6+3 ; If DTW5 = 0 then jump down to DA6+3 to print a newline ; as the buffer is now empty ; If we get here then we have printed our line but there ; is more in the buffer, so we now want to remove the ; line we just printed from the start of BUF LDY #0 ; Set Y = 0 to count through the characters in BUF INX ; Increment X, so it now contains the number of ; characters in the buffer (as DTW5 is a zero-based ; pointer and is therefore equal to the number of ; characters minus 1) .DAL4 LDA BUF+LL+1,Y ; Copy the Y-th character from BUF+LL to BUF STA BUF,Y INY ; Increment the character pointer DEX ; Decrement the character count BNE DAL4 ; Loop back to copy the next character until we have ; shuffled down the whole buffer BEQ DA5 ; Jump back to DA5 (this BEQ is effectively a JMP as we ; have already passed through the BNE above) .DAS1 ; This subroutine prints out X characters from BUF, ; returning with X = 0 LDY #0 ; Set Y = 0 to point to the first character in BUF .DAL5 LDA BUF,Y ; Print the Y-th character in BUF using CHPR, which also JSR CHPR ; clears the C flag for when we return from the ; subroutine below INY ; Increment Y to point to the next character DEX ; Decrement the loop counter BNE DAL5 ; Loop back for the next character until we have printed ; X characters from BUF .dec27 RTS ; Return from the subroutine .DA6 JSR DAS1 ; Call DAS1 to print X characters from BUF, returning ; with X = 0 STX DTW5 ; Set the buffer size in DTW5 to 0, as the buffer is now ; empty PLA ; Restore Y and X from the stack TAY PLA TAX LDA #12 ; Set A = 12, so when we skip BELL and fall through into ; CHPR, we print character 12, which is a newline .DA7 EQUB $2C ; Skip the next instruction by turning it into ; $2C $A9 $07, or BIT $07A9, which does nothing apart ; from affect the flags ; Fall through into CHPR (skipping BELL) to print the ; character and return with the C flag cleared ; ****************************************************************************** ; ; Name: BELL ; Type: Subroutine ; Category: Sound ; Summary: Make a standard system beep ; ; ------------------------------------------------------------------------------ ; ; This is the standard system beep, as made by the ASCII 7 "BELL" control code. ; ; ****************************************************************************** .BELL LDA #7 ; Control code 7 makes a beep, so load this into A JMP CHPR ; Call the CHPR print routine to actually make the sound ; ****************************************************************************** ; ; Name: DIALS (Part 1 of 4) ; Type: Subroutine ; Category: Dashboard ; Summary: Update the dashboard: speed indicator ; Deep dive: The dashboard indicators ; ; ------------------------------------------------------------------------------ ; ; This routine updates the dashboard. First we draw all the indicators in the ; right part of the dashboard, from top (speed) to bottom (energy banks), and ; then we move on to the left part, again drawing from top (forward shield) to ; bottom (altitude). ; ; This first section starts us off with the speedometer in the top right. ; ; ****************************************************************************** .DIALS LDA #LO(DLOC%+8*30) ; Set SC(1 0) to the screen bitmap address for the STA SC ; character block containing the left end of the top LDA #HI(DLOC%+8*30) ; indicator in the right part of the dashboard, the one STA SC+1 ; showing our speed ; ; DLOC% is the screen address of the dashboard (which ; starts on character row 18) so this sets the address ; to character 30 on that row JSR PZW ; Call PZW to set A to the colour for dangerous values ; and X to the colour for safe values STX K+1 ; Set K+1 (the colour we should show for low values) to ; X (the colour to use for safe values) STA K ; Set K (the colour we should show for high values) to ; A (the colour to use for dangerous values) ; The above sets the following indicators to show red ; for high values and yellow/white for low values LDA #14 ; Set T1 to 14, the threshold at which we change the STA T1 ; indicator's colour LDA DELTA ; Fetch our ship's speed into A, in the range 0-40 ;LSR A ; Draw the speed indicator using a range of 0-31, and JSR DIL-1 ; increment SC to point to the next indicator (the roll ; indicator). The LSR is commented out as it isn't ; required with a call to DIL-1, so perhaps this was ; originally a call to DIL that got optimised ; ****************************************************************************** ; ; Name: DIALS (Part 2 of 4) ; Type: Subroutine ; Category: Dashboard ; Summary: Update the dashboard: pitch and roll indicators ; Deep dive: The dashboard indicators ; ; ****************************************************************************** LDA #0 ; Set R = P = 0 for the low bytes in the call to the ADD STA R ; routine below STA P LDA #8 ; Set S = 8, which is the value of the centre of the STA S ; roll indicator LDA ALP1 ; Fetch the roll angle alpha as a value between 0 and LSR A ; 31, and divide by 4 to get a value of 0 to 7 LSR A ORA ALP2 ; Apply the roll sign to the value, and flip the sign, EOR #%10000000 ; so it's now in the range -7 to +7, with a positive ; roll angle alpha giving a negative value in A JSR ADD ; We now add A to S to give us a value in the range 1 to ; 15, which we can pass to DIL2 to draw the vertical ; bar on the indicator at this position. We use the ADD ; routine like this: ; ; (A X) = (A 0) + (S 0) ; ; and just take the high byte of the result. We use ADD ; rather than a normal ADC because ADD separates out the ; sign bit and does the arithmetic using absolute values ; and separate sign bits, which we want here rather than ; the two's complement that ADC uses JSR DIL2 ; Draw a vertical bar on the roll indicator at offset A ; and increment SC to point to the next indicator (the ; pitch indicator) LDA BETA ; Fetch the pitch angle beta as a value between -8 and ; +8 LDX BET1 ; Fetch the magnitude of the pitch angle beta, and if it BEQ P%+4 ; is 0 (i.e. we are not pitching), skip the next ; instruction SBC #1 ; The pitch angle beta is non-zero, so set A = A - 1 ; (the C flag is set by the call to DIL2 above, so we ; don't need to do a SEC). This gives us a value of A ; from -7 to +7 because these are magnitude-based ; numbers with sign bits, rather than two's complement ; numbers JSR ADD ; We now add A to S to give us a value in the range 1 to ; 15, which we can pass to DIL2 to draw the vertical ; bar on the indicator at this position (see the JSR ADD ; above for more on this) JSR DIL2 ; Draw a vertical bar on the pitch indicator at offset A ; and increment SC to point to the next indicator (the ; four energy banks) ; ****************************************************************************** ; ; Name: DIALS (Part 3 of 4) ; Type: Subroutine ; Category: Dashboard ; Summary: Update the dashboard: four energy banks ; Deep dive: The dashboard indicators ; ; ------------------------------------------------------------------------------ ; ; This and the next section only run once every four iterations of the main ; loop, so while the speed, pitch and roll indicators update every iteration, ; the other indicators update less often. ; ; ****************************************************************************** LDA MCNT ; Fetch the main loop counter and calculate MCNT mod 4, AND #3 ; jumping to dec27 if it is non-zero. dec27 contains an BNE dec27 ; RTS, so the following code only runs every 4 ; iterations of the main loop, otherwise we return from ; the subroutine LDY #0 ; Set Y = 0, for use in various places below JSR PZW ; Call PZW to set A to the colour for dangerous values ; and X to the colour for safe values STX K ; Set K (the colour we should show for high values) to X ; (the colour to use for safe values) STA K+1 ; Set K+1 (the colour we should show for low values) to ; A (the colour to use for dangerous values) ; The above sets the following indicators to show red ; for low values and yellow/white for high values, which ; we use not only for the energy banks, but also for the ; shield levels and current fuel LDX #3 ; Set up a counter in X so we can zero the four bytes at ; XX12, so we can then calculate each of the four energy ; banks' values before drawing them later STX T1 ; Set T1 to 3, the threshold at which we change the ; indicator's colour .DLL23 STY XX12,X ; Set the X-th byte of XX12 to 0 DEX ; Decrement the counter BPL DLL23 ; Loop back for the next byte until the four bytes at ; XX12 are all zeroed LDX #3 ; Set up a counter in X to loop through the 4 energy ; bank indicators, so we can calculate each of the four ; energy banks' values and store them in XX12 LDA ENERGY ; Set A = Q = ENERGY / 4, so they are both now in the LSR A ; range 0-63 (so that's a maximum of 16 in each of the LSR A ; banks, and a maximum of 15 in the top bank) STA Q ; Set Q to A, so we can use Q to hold the remaining ; energy as we work our way through each bank, from the ; full ones at the bottom to the empty ones at the top .DLL24 SEC ; Set A = A - 16 to reduce the energy count by a full SBC #16 ; bank BCC DLL26 ; If the C flag is clear then A < 16, so this bank is ; not full to the brim, and is therefore the last one ; with any energy in it, so jump to DLL26 STA Q ; This bank is full, so update Q with the energy of the ; remaining banks LDA #16 ; Store this bank's level in XX12 as 16, as it is full, STA XX12,X ; with XX12+3 for the bottom bank and XX12+0 for the top LDA Q ; Set A to the remaining energy level again DEX ; Decrement X to point to the next bank, i.e. the one ; above the bank we just processed BPL DLL24 ; Loop back to DLL24 until we have either processed all ; four banks, or jumped out early to DLL26 if the top ; banks have no charge BMI DLL9 ; Jump to DLL9 as we have processed all four banks (this ; BMI is effectively a JMP as A will never be positive) .DLL26 LDA Q ; If we get here then the bank we just checked is not STA XX12,X ; fully charged, so store its value in XX12 (using Q, ; which contains the energy of the remaining banks - ; i.e. this one) ; Now that we have the four energy bank values in XX12, ; we can draw them, starting with the top bank in XX12 ; and looping down to the bottom bank in XX12+3, using Y ; as a loop counter, which was set to 0 above .DLL9 LDA XX12,Y ; Fetch the value of the Y-th indicator, starting from ; the top STY P ; Store the indicator number in P for retrieval later JSR DIL ; Draw the energy bank using a range of 0-15, and ; increment SC to point to the next indicator (the ; next energy bank down) LDY P ; Restore the indicator number into Y INY ; Increment the indicator number CPY #4 ; Check to see if we have drawn the last energy bank BNE DLL9 ; Loop back to DLL9 if we have more banks to draw, ; otherwise we are done ; ****************************************************************************** ; ; Name: DIALS (Part 4 of 4) ; Type: Subroutine ; Category: Dashboard ; Summary: Update the dashboard: shields, fuel, laser & cabin temp, altitude ; Deep dive: The dashboard indicators ; ; ****************************************************************************** LDA #LO(DLOC%+8*6) ; Set SC(1 0) to the screen bitmap address for the STA SC ; character block containing the left end of the top LDA #HI(DLOC%+8*6) ; indicator in the left part of the dashboard, the one STA SC+1 ; showing the forward shield ; ; DLOC% is the screen address of the dashboard (which ; starts on character row 18) so this sets the address ; to character 8 on that row LDA #YELLOW ; Set K (the colour we should show for high values) to STA K ; yellow STA K+1 ; Set K+1 (the colour we should show for low values) to ; yellow, so the fuel indicator always shows in this ; colour LDA FSH ; Draw the forward shield indicator using a range of JSR DILX ; 0-255, and increment SC to point to the next indicator ; (the aft shield) LDA ASH ; Draw the aft shield indicator using a range of 0-255, JSR DILX ; and increment SC to point to the next indicator (the ; fuel level) LDA QQ14 ; Draw the fuel level indicator using a range of 0-63, JSR DILX+2 ; and increment SC to point to the next indicator (the ; cabin temperature) JSR PZW ; Call PZW to set A to the colour for dangerous values ; and X to the colour for safe values STX K+1 ; Set K+1 (the colour we should show for low values) to ; X (the colour to use for safe values) STA K ; Set K (the colour we should show for high values) to ; A (the colour to use for dangerous values) ; The above sets the following indicators to show red ; for high values and yellow/white for low values, which ; we use for the cabin and laser temperature bars LDX #11 ; Set T1 to 11, the threshold at which we change the STX T1 ; cabin and laser temperature indicators' colours LDA CABTMP ; Draw the cabin temperature indicator using a range of JSR DILX ; 0-255, and increment SC to point to the next indicator ; (the laser temperature) LDA GNTMP ; Draw the laser temperature indicator using a range of JSR DILX ; 0-255, and increment SC to point to the next indicator ; (the altitude) LDA #240 ; Set T1 to 240, the threshold at which we change the STA T1 ; altitude indicator's colour. As the altitude has a ; range of 0-255, pixel 16 will not be filled in, and ; 240 would change the colour when moving between pixels ; 15 and 16, so this effectively switches off the colour ; change for the altitude indicator ;STA K+1 ; This instruction is commented out in the original ; source LDA ALTIT ; Draw the altitude indicator using a range of 0-255 JSR DILX JMP COMPAS ; We have now drawn all the indicators, so jump to ; COMPAS to draw the compass, returning from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: PZW ; Type: Subroutine ; Category: Dashboard ; Summary: Fetch the current dashboard colours, to support flashing ; ; ------------------------------------------------------------------------------ ; ; Set A and X to the colours we should use for indicators showing dangerous and ; safe values respectively. This enables us to implement flashing indicators, ; which is one of the game's configurable options. ; ; If flashing is enabled, the colour returned in A (dangerous values) will be ; red for 8 iterations of the main loop, and yellow for the next 8, before going ; back to red. If we always use PZW to decide which colours we should use when ; updating indicators, flashing colours will be automatically taken care of for ; us. ; ; The values returned are #YELLOW for yellow and #RED for red. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A The colour to use for indicators with dangerous values ; ; X The colour to use for indicators with safe values ; ; ****************************************************************************** .PZW LDX #YELLOW ; Set X to dashboard colour yellow LDA MCNT ; A will be non-zero for 8 out of every 16 main loop AND #%00001000 ; counts, when bit 4 is set, so this is what we use to ; flash the "danger" colour AND FLH ; A will be zeroed if flashing colours are disabled BEQ P%+4 ; If A is zero, skip to the LDA instruction below TXA ; Otherwise flashing colours are enabled and it's the ; main loop iteration where we flash them, so set A to ; dashboard colour yellow and use the BIT trick below to ; return from the subroutine EQUB $2C ; Skip the next instruction by turning it into ; $2C $A9 $0F, or BIT $0FA9, which does nothing apart ; from affect the flags LDA #RED ; Set A to dashboard colour red RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DILX ; Type: Subroutine ; Category: Dashboard ; Summary: Update a bar-based indicator on the dashboard ; Deep dive: The dashboard indicators ; ; ------------------------------------------------------------------------------ ; ; The range of values shown on the indicator depends on which entry point is ; called. For the default entry point of DILX, the range is 0-255 (as the value ; passed in A is one byte). The other entry points are shown below. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The value to be shown on the indicator (so the larger ; the value, the longer the bar) ; ; K The colour to use when A is a high value ; ; K+1 The colour to use when A is a low value ; ; SC(1 0) The screen address of the first character block in the ; indicator ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; DILX+2 The range of the indicator is 0-64 (for the fuel ; indicator) ; ; DIL-1 The range of the indicator is 0-32 (for the speed ; indicator) ; ; DIL The range of the indicator is 0-16 (for the energy ; banks) ; ; ****************************************************************************** .DILX LSR A ; If we call DILX, we set A = A / 16, so A is 0-15 LSR A LSR A ; If we call DILX+2, we set A = A / 4, so A is 0-15 LSR A ; If we call DIL-1, we set A = A / 2, so A is 0-15 .DIL ; If we call DIL, we leave A alone, so A is 0-15 STA Q ; Store the indicator value in Q, now reduced to 0-15, ; which is the length of the indicator to draw in pixels LDX #$FF ; Set R = $FF, to use as a mask for drawing each row of STX R ; each character block of the bar, starting with a full ; character's width of 4 pixels CMP T1 ; If A >= T1 then we have passed the threshold where we BCS DL30 ; change bar colour, so jump to DL30 to set A to the ; "high value" colour LDA K+1 ; Set A to K+1, the "low value" colour to use BNE DL31 ; Jump down to DL31 (this BNE is effectively a JMP as A ; will never be zero) .DL30 LDA K ; Set A to K, the "high value" colour to use .DL31 STA COL ; Store the colour of the indicator in COL LDY #2 ; We want to start drawing the indicator on the third ; line in this character row, so set Y to point to that ; row's offset LDX #3 ; Set up a counter in X for the width of the indicator, ; which is 4 characters (each of which is 4 pixels wide, ; to give a total width of 16 pixels) .DL1 LDA Q ; Fetch the indicator value (0-15) from Q into A CMP #4 ; If Q < 4, then we need to draw the end cap of the BCC DL2 ; indicator, which is less than a full character's ; width, so jump down to DL2 to do this SBC #4 ; Otherwise we can draw a four-pixel wide block, so STA Q ; subtract 4 from Q so it contains the amount of the ; indicator that's left to draw after this character LDA R ; Fetch the shape of the indicator row that we need to ; display from R, so we can use it as a mask when ; painting the indicator. It will be $FF at this point ; (i.e. a full four-pixel row) .DL5 AND COL ; Fetch the four-pixel colour byte from COL, and only ; keep pixels that have their equivalent bits set in the ; mask byte in A STA (SC),Y ; Draw the shape of the mask on pixel row Y of the ; character block we are processing INY ; Draw the next pixel row, incrementing Y STA (SC),Y INY ; And draw the third pixel row, incrementing Y STA (SC),Y TYA ; Add 6 to Y, so Y is now 8 more than when we started CLC ; this loop iteration, so Y now points to the address ADC #6 ; of the first line of the indicator bar in the next ; character block (as each character is 8 bytes of ; screen memory) BCC P%+4 ; If the addition of the low bytes of SC overflowed, INC SC+1 ; increment the high byte TAY ; Transfer the updated value (Y + 6) back into Y DEX ; Decrement the loop counter for the next character ; block along in the indicator BMI DL6 ; If we just drew the last character block then we are ; done drawing, so jump down to DL6 to finish off BPL DL1 ; Loop back to DL1 to draw the next character block of ; the indicator (this BPL is effectively a JMP as A will ; never be negative following the previous BMI) .DL2 EOR #3 ; If we get here then we are drawing the indicator's STA Q ; end cap, so Q is < 4, and this EOR flips the bits, so ; instead of containing the number of indicator columns ; we need to fill in on the left side of the cap's ; character block, Q now contains the number of blank ; columns there should be on the right side of the cap's ; character block LDA R ; Fetch the current mask from R, which will be $FF at ; this point, so we need to turn Q of the columns on the ; right side of the mask to black to get the correct end ; cap shape for the indicator .DL3 ASL A ; Shift the mask left so bits 0 and 1 are cleared ASL A DEC Q ; Decrement the counter for the number of columns to ; blank out BPL DL3 ; If we still have columns to blank out in the mask, ; loop back to DL3 until the mask is correct for the ; end cap PHA ; Store the mask byte on the stack while we use the ; accumulator for a bit LDA #0 ; Change the mask so no bits are set, so the characters STA R ; after the one we're about to draw will be all blank LDA #99 ; Set Q to a high number (99, why not) so we will keep STA Q ; drawing blank characters until we reach the end of ; the indicator row PLA ; Restore the mask byte from the stack so we can use it ; to draw the end cap of the indicator JMP DL5 ; Jump back up to DL5 to draw the mask byte on-screen .DL6 ; We now need to move down into the character block ; below, and each character row in screen memory takes ; up $140 bytes ($100 for the visible part and $20 for ; each of the blank borders on the side of the screen), ; so that's what we need to add to SC(1 0) LDA SC ; Set SC(1 0) = SC(1 0) + $140 CLC ; ADC #$40 ; Starting with the low bytes STA SC LDA SC+1 ; And then adding the high bytes ADC #$01 STA SC+1 .DL9 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DIL2 ; Type: Subroutine ; Category: Dashboard ; Summary: Update the roll or pitch indicator on the dashboard ; Deep dive: The dashboard indicators ; ; ------------------------------------------------------------------------------ ; ; The indicator can show a vertical bar in 16 positions, with a value of 8 ; showing the bar in the middle of the indicator. ; ; In practice this routine is only ever called with A in the range 1 to 15, so ; the vertical bar never appears in the leftmost position (though it does appear ; in the rightmost). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The offset of the vertical bar to show in the indicator, ; from 0 at the far left, to 8 in the middle, and 15 at ; the far right ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag The C flag is set ; ; ****************************************************************************** .DIL2 LDY #1 ; We want to start drawing the vertical indicator bar on ; the second line in the indicator's character block, so ; set Y to point to that row's offset STA Q ; Store the offset of the vertical bar to draw in Q ; We are now going to work our way along the indicator ; on the dashboard, from left to right, working our way ; along one character block at a time. Y will be used as ; a pixel row counter to work our way through the ; character blocks, so each time we draw a character ; block, we will increment Y by 8 to move on to the next ; block (as each character block contains 8 rows) .DLL10 SEC ; Set A = Q - 4, so that A contains the offset of the LDA Q ; vertical bar from the start of this character block SBC #4 BCS DLL11 ; If Q >= 4 then the character block we are drawing does ; not contain the vertical indicator bar, so jump to ; DLL11 to draw a blank character block LDA #$FF ; Set A to a high number (and $FF is as high as they go) LDX Q ; Set X to the offset of the vertical bar, which we know ; is within this character block STA Q ; Set Q to a high number ($FF, why not) so we will keep ; drawing blank characters after this one until we reach ; the end of the indicator row LDA CTWOS,X ; CTWOS is a table of ready-made two-pixel multicolour ; bitmap mode bytes ; ; This fetches a two-pixel multicolour bitmap mode byte ; with the pixel position at 2 * X, so the pixel is at ; the offset that we want for our vertical bar AND #YELLOW ; The four-pixel multicolour bitmap mode colour byte in ; YELLOW represents four pixels of colour %10 (3), which ; is yellow in the dashboard palette. We AND this with A ; so that we only keep the pixel that matches the ; position of the vertical bar (i.e. A is acting as a ; mask on the four-pixel colour byte) JMP DLL12 ; Jump to DLL12 to skip the code for drawing a blank, ; and move on to drawing the indicator .DLL11 ; If we get here then we want to draw a blank for this ; character block STA Q ; Update Q with the new offset of the vertical bar, so ; it becomes the offset after the character block we ; are about to draw LDA #0 ; Change the mask so no bits are set, so all of the ; character blocks we display from now on will be blank .DLL12 STA (SC),Y ; Draw the shape of the mask on pixel row Y of the ; character block we are processing INY ; Draw the next pixel row, incrementing Y STA (SC),Y INY ; And draw the third pixel row, incrementing Y STA (SC),Y INY ; And draw the fourth pixel row, incrementing Y STA (SC),Y TYA ; Add 5 to Y, so Y is now 8 more than when we started CLC ; this loop iteration, so Y now points to the address ADC #5 ; of the first line of the indicator bar in the next TAY ; character block (as each character is 8 bytes of ; screen memory) CPY #30 ; If Y < 30 then we still have some more character BCC DLL10 ; blocks to draw, so loop back to DLL10 to display the ; next one along ; We now need to move down into the character block ; below, and each character row in screen memory takes ; up $140 bytes ($100 for the visible part and $20 for ; each of the blank borders on the side of the screen), ; so that's what we need to add to SC(1 0) ; ; We also know the C flag is set, as we didn't take the ; BCC above, so we can add $13F in order to get the ; correct result LDA SC ; Set SC(1 0) = SC(1 0) + $140 ADC #$3F ; STA SC ; Starting with the low bytes LDA SC+1 ; And then adding the high bytes ADC #$01 STA SC+1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: ESCAPE ; Type: Subroutine ; Category: Flight ; Summary: Launch our escape pod ; ; ------------------------------------------------------------------------------ ; ; This routine displays our doomed Cobra Mk III disappearing off into the ether ; before arranging our replacement ship. Called when we press ESCAPE during ; flight and have an escape pod fitted. ; ; ****************************************************************************** .ESCAPE JSR RES2 ; Reset a number of flight variables and workspaces LDX #CYL ; Set the current ship type to a Cobra Mk III, so we STX TYPE ; can show our ship disappear into the distance when we ; eject in our pod JSR FRS1 ; Call FRS1 to launch the Cobra Mk III straight ahead, ; like a missile launch, but with our ship instead BCS ES1 ; If the Cobra was successfully added to the local ; bubble, jump to ES1 to skip the following instructions LDX #CYL2 ; The Cobra wasn't added to the local bubble for some JSR FRS1 ; reason, so try launching a pirate Cobra Mk III instead .ES1 LDA #8 ; Set the Cobra's byte #27 (speed) to 8 STA INWK+27 LDA #194 ; Set the Cobra's byte #30 (pitch counter) to 194, so it STA INWK+30 ; pitches up as we pull away LSR A ; Set the Cobra's byte #32 (AI flag) to %01100001, so it STA INWK+32 ; has no AI, and we can use this value as a counter to ; do the following loop 97 times .ESL1 JSR MVEIT ; Call MVEIT to move the Cobra in space JSR LL9 ; Call LL9 to draw the Cobra on-screen DEC INWK+32 ; Decrement the counter in byte #32 BNE ESL1 ; Loop back to keep moving the Cobra until the AI flag ; is 0, which gives it time to drift away from our pod JSR SCAN ; Call SCAN to remove the Cobra from the scanner (by ; redrawing it) LDA #0 ; Set A = 0 so we can use it to zero the contents of ; the cargo hold LDX #16 ; We lose all our cargo when using our escape pod, so ; up a counter in X so we can zero the 17 cargo slots ; in QQ20 .ESL2 STA QQ20,X ; Set the X-th byte of QQ20 to zero, so we no longer ; have any of item type X in the cargo hold DEX ; Decrement the counter BPL ESL2 ; Loop back to ESL2 until we have emptied the entire ; cargo hold STA FIST ; Launching an escape pod also clears our criminal ; record, so set our legal status in FIST to 0 ("clean") STA ESCP ; The escape pod is a one-use item, so set ESCP to 0 so ; we no longer have one fitted LDA TRIBBLE ; If there are no Trumbles in our hold, then both bytes ORA TRIBBLE+1 ; of TRIBBLE(1 0) will be zero, so jump to nosurviv to BEQ nosurviv ; skip the following JSR DORND ; Otherwise set TRIBBLE(1 0) to a random number between AND #7 ; 1 and 7, to determine how many Trumbles manage to ORA #1 ; hitch a ride in the escape pod (so using an escape pod STA TRIBBLE ; is not a solution to the trouble with Trumbles) LDA #0 STA TRIBBLE+1 .nosurviv LDA #70 ; Our replacement ship is delivered with a full tank of STA QQ14 ; fuel, so set the current fuel level in QQ14 to 70, or ; 7.0 light years JMP GOIN ; Go to the docking bay (i.e. show the Status Mode ; screen) and return from the subroutine with a tail ; call ; ****************************************************************************** ; ; Name: HME2 ; Type: Subroutine ; Category: Charts ; Summary: Search the galaxy for a system ; ; ****************************************************************************** .HME2 ;LDA #CYAN ; These instructions are commented out in the original ;JSR DOCOL ; source LDA #14 ; Print extended token 14 ("{clear bottom of screen} JSR DETOK ; PLANET NAME?{fetch line input from keyboard}"). The ; last token calls MT26, which puts the entered search ; term in INWK+5 and the term length in Y JSR TT103 ; Draw small crosshairs at coordinates (QQ9, QQ10), ; which will erase the crosshairs currently there JSR TT81 ; Set the seeds in QQ15 (the selected system) to those ; of system 0 in the current galaxy (i.e. copy the seeds ; from QQ21 to QQ15) LDA #0 ; We now loop through the galaxy's systems in order, STA XX20 ; until we find a match, so set XX20 to act as a system ; counter, starting with system 0 .HME3 JSR MT14 ; Switch to justified text when printing extended ; tokens, so the call to cpl prints into the justified ; text buffer at BUF instead of the screen, and DTW5 ; gets set to the length of the system name JSR cpl ; Print the selected system name into the justified text ; buffer LDX DTW5 ; Fetch DTW5 into X, so X is now equal to the length of ; the selected system name LDA INWK+5,X ; Fetch the X-th character from the entered search term CMP #13 ; If the X-th character is not a carriage return, then BNE HME6 ; the selected system name and the entered search term ; are different lengths, so jump to HME6 to move on to ; the next system .HME4 DEX ; Decrement X so it points to the last letter of the ; selected system name (and, when we loop back here, it ; points to the next letter to the left) LDA INWK+5,X ; Set A to the X-th character of the entered search term ORA #%00100000 ; Set bit 5 of the character to make it lower case CMP BUF,X ; If the character in A matches the X-th character of BEQ HME4 ; the selected system name in BUF, loop back to HME4 to ; check the next letter to the left TXA ; The last comparison didn't match, so copy the letter BMI HME5 ; number into A, and if it's negative, that means we ; managed to go past the first letters of each term ; before we failed to get a match, so the terms are the ; same, so jump to HME5 to process a successful search .HME6 ; If we get here then the selected system name and the ; entered search term did not match JSR TT20 ; We want to move on to the next system, so call TT20 ; to twist the three 16-bit seeds in QQ15 INC XX20 ; Increment the system counter in XX20 BNE HME3 ; If we haven't yet checked all 256 systems in the ; current galaxy, loop back to HME3 to check the next ; system ; If we get here then the entered search term did not ; match any systems in the current galaxy JSR TT111 ; Select the system closest to galactic coordinates ; (QQ9, QQ10), so we can put the crosshairs back where ; they were before the search JSR TT103 ; Draw small crosshairs at coordinates (QQ9, QQ10) LDY #sfxboop ; Call the NOISE routine with Y = sfxboop to make a low, JSR NOISE ; long beep to indicate a failed search LDA #215 ; Print extended token 215 ("{left align} UNKNOWN JMP DETOK ; PLANET"), which will print on-screen as the left align ; code disables justified text, and return from the ; subroutine using a tail call .HME5 ; If we get here then we have found a match for the ; entered search LDA QQ15+3 ; The x-coordinate of the system described by the seeds STA QQ9 ; in QQ15 is in QQ15+3 (s1_hi), so we copy this to QQ9 ; as the x-coordinate of the search result LDA QQ15+1 ; The y-coordinate of the system described by the seeds STA QQ10 ; in QQ15 is in QQ15+1 (s0_hi), so we copy this to QQ10 ; as the y-coordinate of the search result JSR TT111 ; Select the system closest to galactic coordinates ; (QQ9, QQ10) JSR TT103 ; Draw small crosshairs at coordinates (QQ9, QQ10) JSR MT15 ; Switch to left-aligned text when printing extended ; tokens so future tokens will print to the screen (as ; this disables justified text) JMP T95 ; Jump to T95 to print the distance to the selected ; system and return from the subroutine using a tail ; call ; ****************************************************************************** ; ; Save ELTB.bin ; ; ****************************************************************************** PRINT "ELITE B" PRINT "Assembled at ", ~CODE_B% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_B%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_B% PRINT "S.ELTB ", ~CODE_B%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_B% SAVE "3-assembled-output/ELTB.bin", CODE_B%, P%, LOAD% ; ****************************************************************************** ; ; ELITE C FILE ; ; Produces the binary file ELTC.bin that gets loaded by elite-checksum.py. ; ; ****************************************************************************** CODE_C% = P% LOAD_C% = LOAD% +P% - CODE% ; ****************************************************************************** ; ; Name: TACTICS (Part 1 of 7) ; Type: Subroutine ; Category: Tactics ; Summary: Apply tactics: Process missiles, both enemy missiles and our own ; Deep dive: Program flow of the tactics routine ; ; ------------------------------------------------------------------------------ ; ; This section implements missile tactics and is entered at TA18 from the main ; entry point below, if the current ship is a missile. Specifically: ; ; * If E.C.M. is active, destroy the missile ; ; * If the missile is hostile towards us, then check how close it is. If it ; hasn't reached us, jump to part 3 so it can streak towards us, otherwise ; we've been hit, so process a large amount of damage to our ship ; ; * Otherwise see how close the missile is to its target. If it has not yet ; reached its target, give the target a chance to activate its E.C.M. if it ; has one, otherwise jump to TA19 with K3 set to the vector from the target ; to the missile ; ; * If it has reached its target and the target is the space station, destroy ; the missile, potentially damaging us if we are nearby ; ; * If it has reached its target and the target is a ship, destroy the missile ; and the ship, potentially damaging us if we are nearby ; ; ****************************************************************************** .TA352 ; If we get here, the missile has been destroyed by ; E.C.M. or by the space station LDA INWK ; Set A = x_lo OR y_lo OR z_lo of the missile ORA INWK+3 ORA INWK+6 BNE TA872 ; If A is non-zero then the missile is not near our ; ship, so skip the next two instructions to avoid ; damaging our ship LDA #80 ; Otherwise the missile just got destroyed near us, so JSR OOPS ; call OOPS to damage the ship by 80, which is nowhere ; near as bad as the 250 damage from a missile slamming ; straight into us, but it's still pretty nasty .TA872 LDX #PLT ; Set X to the ship type for plate alloys, so we get ; awarded the kill points for the missile scraps in TA87 BNE TA353 ; Jump to TA353 to process the missile kill tally and ; make an explosion sound .TA34 ; If we get here, the missile is hostile LDA #0 ; Set A to x_hi OR y_hi OR z_hi JSR MAS4 BEQ P%+5 ; If A = 0 then the missile is very close to our ship, ; so skip the following instruction JMP TN4 ; Jump down to part 3 to set up the vectors and skip ; straight to aggressive manoeuvring JSR TA873 ; The missile has hit our ship, so call TA873 to set ; bit 7 of the missile's byte #31, which marks the ; missile as being killed JSR EXNO3 ; Make the sound of the missile exploding LDA #250 ; Call OOPS to damage the ship by 250, which is a pretty JMP OOPS ; big hit, and return from the subroutine using a tail ; call .TA18 ; This is the entry point for missile tactics and is ; called from the main TACTICS routine below LDA ECMA ; If an E.C.M. is currently active (either ours or an BNE TA352 ; opponent's), jump to TA352 to destroy this missile LDA INWK+32 ; Fetch the AI flag from byte #32 and if bit 6 is set ASL A ; (i.e. missile is hostile), jump up to TA34 to check BMI TA34 ; whether the missile has hit us LSR A ; Otherwise shift A right again. We know bits 6 and 7 ; are now clear, so this leaves bits 0-5. Bits 1-5 ; contain the target's slot number, and bit 0 is cleared ; in FRMIS when a missile is launched, so A contains ; the slot number shifted left by 1 (i.e. doubled) so we ; can use it as an index for the two-byte address table ; at UNIV TAX ; Copy the address of the target ship's data block from LDA UNIV,X ; UNIV(X+1 X) to (A V) STA V LDA UNIV+1,X JSR VCSUB ; Calculate vector K3 as follows: ; ; K3(2 1 0) = (x_sign x_hi x_lo) - x-coordinate of ; target ship ; ; K3(5 4 3) = (y_sign y_hi z_lo) - y-coordinate of ; target ship ; ; K3(8 7 6) = (z_sign z_hi z_lo) - z-coordinate of ; target ship ; So K3 now contains the vector from the target ship to ; the missile LDA K3+2 ; Set A = OR of all the sign and high bytes of the ORA K3+5 ; above, clearing bit 7 (i.e. ignore the signs) ORA K3+8 AND #%01111111 ORA K3+1 ORA K3+4 ORA K3+7 BNE TA64 ; If the result is non-zero, then the missile is some ; distance from the target, so jump down to TA64 see if ; the target activates its E.C.M. LDA INWK+32 ; Fetch the AI flag from byte #32 and if only bits 7 and CMP #%10000010 ; 1 are set (AI is enabled and the target is slot 1, the BEQ TA352 ; space station), jump to TA352 to destroy this missile, ; as the space station ain't kidding around LDY #31 ; Fetch byte #31 (the exploding flag) of the target ship LDA (V),Y ; into A BIT M32+1 ; M32 contains an LDY #32 instruction, so M32+1 contains ; 32, so this instruction tests A with %00100000, which ; checks bit 5 of A (the "already exploding?" bit) BNE TA35 ; If the target ship is already exploding, jump to TA35 ; to destroy this missile ORA #%10000000 ; Otherwise set bit 7 of the target's byte #31 to mark STA (V),Y ; the ship as having been killed, so it explodes .TA35 LDA INWK ; Set A = x_lo OR y_lo OR z_lo of the missile ORA INWK+3 ORA INWK+6 BNE TA87 ; If A is non-zero then the missile is not near our ; ship, so jump to TA87 to skip damaging our ship LDA #80 ; Otherwise the missile just got destroyed near us, so JSR OOPS ; call OOPS to damage the ship by 80, which is nowhere ; near as bad as the 250 damage from a missile slamming ; straight into us, but it's still pretty nasty .TA87 LDA INWK+32 ; Set X to bits 1-6 of the missile's AI flag in ship AND #%01111111 ; byte #32, so that bits 0-4 of X are the target's slot LSR A ; number, and bit 5 is clear (as the missile is ours) TAX ; ; So X contains the slot number of the target ship ; ; We should now fetch the ship type from slot X, so we ; can pass the ship type to EXNO2 to add the correct ; number of kill points to award for this type of ship, ; but instead we're passing the slot number to EXNO2 ; ; This is a bug that means we will be allocated a fairly ; random number of kill points when destroying a ship ; with a missile; this bug was fixed in the NES version, ; but it affects the Commodore 64, Apple II and BBC ; Master versions of Elite .TA353 JSR EXNO2 ; Call EXNO2 to process the fact that we have killed a ; missile (so increase the kill tally, make an explosion ; sound and so on) .TA873 ASL INWK+31 ; Set bit 7 of the missile's byte #31 flag to mark it as SEC ; having been killed, so it explodes ROR INWK+31 .TA1 RTS ; Return from the subroutine .TA64 ; If we get here then the missile has not reached the ; target JSR DORND ; Set A and X to random numbers CMP #16 ; If A >= 16 (94% chance), jump down to TA19S with the BCS TA19S ; vector from the target to the missile in K3 .M32 LDY #32 ; Fetch byte #32 for the target and shift bit 0 (E.C.M.) LDA (V),Y ; into the C flag LSR A BCS P%+5 ; If the C flag is set then the target has E.C.M. ; fitted, so skip the next instruction .TA19S JMP TA19 ; The target does not have E.C.M. fitted, so jump down ; to TA19 with the vector from the target to the missile ; in K3 JMP ECBLB2 ; The target has E.C.M., so jump to ECBLB2 to set it ; off, returning from the subroutine using a tail call ; ****************************************************************************** ; ; Name: TACTICS (Part 2 of 7) ; Type: Subroutine ; Category: Tactics ; Summary: Apply tactics: Escape pod, station, lone Thargon, safe-zone pirate ; Deep dive: Program flow of the tactics routine ; Aggression and hostility in ship tactics ; ; ------------------------------------------------------------------------------ ; ; This section contains the main entry point at TACTICS, which is called from ; part 2 of MVEIT for ships that have the AI flag set (i.e. bit 7 of byte #32). ; This part does the following: ; ; * If this is a missile, jump up to the missile code in part 1 ; ; * If this is the space station and it is hostile, consider spawning a cop ; (6.2% chance, up to a maximum of seven) and we're done ; ; * If this is the space station and it is not hostile, consider spawning ; (0.8% chance if there are no Transporters around) a Transporter or Shuttle ; (equal odds of each type) and we're done ; ; * If this is a rock hermit, consider spawning (22% chance) a highly ; aggressive and hostile Sidewinder, Mamba, Krait, Adder or Gecko (equal ; odds of each type) and we're done ; ; * Recharge the ship's energy banks by 1 ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The ship type ; ; ****************************************************************************** .TACTICS LDA #3 ; Set RAT = 3, which is the magnitude we set the pitch STA RAT ; or roll counter to in part 7 when turning a ship ; towards a vector (a higher value giving a longer ; turn). This value is not changed in the TACTICS ; routine, but it is set to different values by the ; DOCKIT routine LDA #4 ; Set RAT2 = 4, which is the threshold below which we STA RAT2 ; don't apply pitch and roll to the ship (so a lower ; value means we apply pitch and roll more often, and a ; value of 0 means we always apply them). The value is ; compared with double the high byte of sidev . XX15, ; where XX15 is the vector from the ship to the enemy ; or planet. This value is set to different values by ; both the TACTICS and DOCKIT routines LDA #22 ; Set CNT2 = 22, which is the maximum angle beyond which STA CNT2 ; a ship will slow down to start turning towards its ; prey (a lower value means a ship will start to slow ; down even if its angle with the enemy ship is large, ; which gives a tighter turn). This value is not changed ; in the TACTICS routine, but it is set to different ; values by the DOCKIT routine CPX #MSL ; If this is a missile, jump up to TA18 to implement BEQ TA18 ; missile tactics CPX #SST ; If this is not the space station, jump down to TA13 BNE TA13 LDA NEWB ; This is the space station, so check whether bit 2 of AND #%00000100 ; the ship's NEWB flags is set, and if it is (i.e. the BNE TN5 ; station is hostile), jump to TN5 to spawn some cops LDA MANY+SHU+1 ; The station is not hostile, so check how many BNE TA1 ; Transporters there are in the vicinity, and if we ; already have one, return from the subroutine (as TA1 ; contains an RTS) ; If we get here then the station is not hostile, so we ; can consider spawning a Transporter or Shuttle JSR DORND ; Set A and X to random numbers CMP #253 ; If A < 253 (99.2% chance), return from the subroutine BCC TA1 ; (as TA1 contains an RTS) AND #1 ; Set A = a random number that's either 0 or 1 ADC #SHU-1 ; The C flag is set (as we didn't take the BCC above), TAX ; so this sets X to a value of either #SHU or #SHU + 1, ; which is the ship type for a Shuttle or a Transporter BNE TN6 ; Jump to TN6 to spawn this ship type and return from ; the subroutine using a tail call (this BNE is ; effectively a JMP as A is never zero) .TN5 ; If we get here then this is the space station and it ; is hostile, so we need to spawn some cops JSR DORND ; Set A and X to random numbers CMP #240 ; If A < 240 (93.8% chance), return from the subroutine BCC TA1 ; (as TA1 contains an RTS) LDA MANY+COPS ; Check how many cops there are in the vicinity already, CMP #4 ; and if there are 4 or more, return from the subroutine BCS TA22 ; (as TA22 contains an RTS) LDX #COPS ; Set X to the ship type for a cop .TN6 LDA #%11110001 ; Set the AI flag to give the ship E.C.M., enable AI and ; make it very aggressive (56 out of 63) JMP SFS1 ; Jump to SFS1 to spawn the ship, returning from the ; subroutine using a tail call .TA13 CPX #HER ; If this is not a rock hermit, jump down to TA17 BNE TA17 JSR DORND ; Set A and X to random numbers CMP #200 ; If A < 200 (78% chance), return from the subroutine BCC TA22 ; (as TA22 contains an RTS) LDX #0 ; Set byte #32 to %00000000 to disable AI, zero the STX INWK+32 ; aggression level and remove E.C.M. LDX #%00100100 ; Set the ship's NEWB flags to %00100100 so the ship we STX NEWB ; spawn below will inherit the default values from E% as ; well as having bit 2 (hostile) and bit 5 (innocent ; bystander) set AND #3 ; Set A = a random number that's in the range 0-3 ADC #SH3 ; The C flag is set (as we didn't take the BCC above), TAX ; so this sets X to a random value between #SH3 + 1 and ; #SH3 + 4, so that's a Sidewinder, Mamba, Krait, Adder ; or Gecko JSR TN6 ; Call TN6 to spawn this ship with E.C.M., AI and a high ; aggression (56 out of 63), though we override this in ; the next instructions LDA #0 ; Set byte #32 to %00000000 to disable AI, zero the STA INWK+32 ; aggression level and remove E.C.M. (for the rock ; hermit) RTS ; Return from the subroutine .TA17 LDY #14 ; If the ship's energy is greater or equal to the LDA INWK+35 ; maximum value from the ship's blueprint pointed to by CMP (XX0),Y ; XX0, then skip the next instruction BCS TA21 INC INWK+35 ; The ship's energy is not at maximum, so recharge the ; energy banks by 1 ; ****************************************************************************** ; ; Name: TACTICS (Part 3 of 7) ; Type: Subroutine ; Category: Tactics ; Summary: Apply tactics: Calculate dot product to determine ship's aim ; Deep dive: Program flow of the tactics routine ; Aggression and hostility in ship tactics ; ; ------------------------------------------------------------------------------ ; ; This section sets up some vectors and calculates dot products. Specifically: ; ; * If this is a lone Thargon without a mothership, set it adrift aimlessly ; and we're done ; ; * If this is a trader, 80% of the time we're done, 20% of the time the ; trader performs the same checks as the bounty hunter ; ; * If this is a bounty hunter (or one of the 20% of traders) and we have been ; really bad (i.e. a fugitive or serious offender), the ship becomes hostile ; (if it isn't already) ; ; * If the ship is not hostile, then either perform docking manoeuvres (if ; it's docking) or fly towards the planet (if it isn't docking) and we're ; done ; ; * If the ship is hostile, and a pirate, and we are within the space station ; safe zone, stop the pirate from attacking by removing all its aggression ; ; * Calculate the dot product of the ship's nose vector (i.e. the direction it ; is pointing) with the vector between us and the ship. This value will help ; us work out later on whether the enemy ship is pointing towards us, and ; therefore whether it can hit us with its lasers. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; GOPL Make the ship head towards the planet ; ; ****************************************************************************** .TA21 CPX #TGL ; If this is not a Thargon, jump down to TA14 BNE TA14 LDA MANY+THG ; If there is at least one Thargoid in the vicinity, BNE TA14 ; jump down to TA14 LSR INWK+32 ; This is a Thargon but there is no Thargoid mothership, ASL INWK+32 ; so clear bit 0 of the AI flag to disable its E.C.M. LSR INWK+27 ; And halve the Thargon's speed .TA22 RTS ; Return from the subroutine .TA14 JSR DORND ; Set A and X to random numbers LDA NEWB ; Extract bit 0 of the ship's NEWB flags into the C flag LSR A ; and jump to TN1 if it is clear (i.e. if this is not a BCC TN1 ; trader) CPX #50 ; This is a trader, so if X >= 50 (80% chance), return BCS TA22 ; from the subroutine (as TA22 contains an RTS) .TN1 LSR A ; Extract bit 1 of the ship's NEWB flags into the C flag BCC TN2 ; and jump to TN2 if it is clear (i.e. if this is not a ; bounty hunter) LDX FIST ; This is a bounty hunter, so check whether our FIST CPX #40 ; rating is < 40 (where 50 is a fugitive), and jump to BCC TN2 ; TN2 if we are not 100% evil LDA NEWB ; We are a fugitive or a bad offender, and this ship is ORA #%00000100 ; a bounty hunter, so set bit 2 of the ship's NEWB flags STA NEWB ; to make it hostile LSR A ; Shift A right twice so the next test in TN2 will check LSR A ; bit 2 .TN2 LSR A ; Extract bit 2 of the ship's NEWB flags into the C flag BCS TN3 ; and jump to TN3 if it is set (i.e. if this ship is ; hostile) LSR A ; The ship is not hostile, so extract bit 4 of the LSR A ; ship's NEWB flags into the C flag, and jump to GOPL if BCC GOPL ; it is clear (i.e. if this ship is not docking) JMP DOCKIT ; The ship is not hostile and is docking, so jump to ; DOCKIT to apply the docking algorithm to this ship .GOPL JSR SPS1 ; The ship is not hostile and it is not docking, so call ; SPS1 to calculate the vector to the planet and store ; it in XX15 JMP TA151 ; Jump to TA151 to make the ship head towards the planet .TN3 LSR A ; Extract bit 3 of the ship's NEWB flags into the C flag BCC TN4 ; and jump to TN4 if it is clear (i.e. if this ship is ; not a pirate) LDA SSPR ; If we are not inside the space station safe zone, jump BEQ TN4 ; to TN4 ; If we get here then this is a pirate and we are inside ; the space station safe zone LDA INWK+32 ; Clear bits 1 to 6 of the AI flag in byte #32 (to set AND #%10000001 ; the aggression level to zero) STA INWK+32 .TN4 LDX #8 ; We now want to copy the ship's x, y and z coordinates ; from INWK to K3, so set up a counter for 9 bytes .TAL1 LDA INWK,X ; Copy the X-th byte from INWK to the X-th byte of K3 STA K3,X DEX ; Decrement the counter BPL TAL1 ; Loop back until we have copied all 9 bytes .TA19 ; If this is a missile that's heading for its target ; (not us, one of the other ships), then the missile ; routine at TA18 above jumps here after setting K3 to ; the vector from the target to the missile JSR TAS2 ; Normalise the vector in K3 and store the normalised ; version in XX15, so XX15 contains the normalised ; vector from our ship to the ship we are applying AI ; tactics to (or the normalised vector from the target ; to the missile - in both cases it's the vector from ; the potential victim to the attacker) LDY #10 ; Set (A X) = nosev . XX15 JSR TAS3 STA CNT ; Store the high byte of the dot product in CNT. The ; bigger the value, the more aligned the two ships are, ; with a maximum magnitude of 36 (96 * 96 >> 8). If CNT ; is positive, the ships are facing in a similar ; direction, if it's negative they are facing in ; opposite directions ; ****************************************************************************** ; ; Name: TACTICS (Part 4 of 7) ; Type: Subroutine ; Category: Tactics ; Summary: Apply tactics: Check energy levels, maybe launch escape pod if low ; Deep dive: Program flow of the tactics routine ; ; ------------------------------------------------------------------------------ ; ; This section works out what kind of condition the ship is in. Specifically: ; ; * If this is an Anaconda, consider spawning (22% chance) a Worm (61% of the ; time) or a Sidewinder (39% of the time) ; ; * Rarely (2.5% chance) roll the ship by a noticeable amount ; ; * If the ship has at least half its energy banks full, jump to part 6 to ; consider firing the lasers ; ; * If the ship is not into the last 1/8th of its energy, jump to part 5 to ; consider firing a missile ; ; * If the ship is into the last 1/8th of its energy, and this ship type has ; an escape pod fitted, then rarely (10% chance) the ship launches an escape ; pod and is left drifting in space ; ; ****************************************************************************** LDA TYPE ; If this is not a missile, skip the following CMP #MSL ; instruction BNE P%+5 JMP TA20 ; This is a missile, so jump down to TA20 to get ; straight into some aggressive manoeuvring CMP #ANA ; If this is not an Anaconda, jump down to TN7 to skip BNE TN7 ; the following JSR DORND ; Set A and X to random numbers CMP #200 ; If A < 200 (78% chance), jump down to TN7 to skip the BCC TN7 ; following JSR DORND ; Set A and X to random numbers LDX #WRM ; Set X to the ship type for a Worm CMP #100 ; If A >= 100 (61% chance), skip the following BCS P%+4 ; instruction LDX #SH3 ; Set X to the ship type for a Sidewinder JMP TN6 ; Jump to TN6 to spawn the Worm or Sidewinder and return ; from the subroutine using a tail call .TN7 JSR DORND ; Set A and X to random numbers CMP #250 ; If A < 250 (97.5% chance), jump down to TA7 to skip BCC TA7 ; the following JSR DORND ; Set A and X to random numbers ORA #104 ; Bump A up to at least 104 and store in the roll STA INWK+29 ; counter, to gives the ship a noticeable roll .TA7 LDY #14 ; Set A = the ship's maximum energy / 2 LDA (XX0),Y LSR A CMP INWK+35 ; If the ship's current energy in byte #35 > A, i.e. the BCC TA3 ; ship has at least half of its energy banks charged, ; jump down to TA3 LSR A ; If the ship's current energy in byte #35 > A / 4, i.e. LSR A ; the ship is not into the last 1/8th of its energy, CMP INWK+35 ; jump down to ta3 to consider firing a missile BCC ta3 JSR DORND ; Set A and X to random numbers CMP #230 ; If A < 230 (90% chance), jump down to ta3 to consider BCC ta3 ; firing a missile LDX TYPE ; Fetch the ship blueprint's default NEWB flags from the LDA E%-1,X ; table at E%, and if bit 7 is clear (i.e. this ship BPL ta3 ; does not have an escape pod), jump to ta3 to skip the ; spawning of an escape pod ; By this point, the ship has run out of both energy and ; luck, so it's time to bail LDA NEWB ; Clear bits 0-3 of the NEWB flags, so the ship is no AND #%11110000 ; longer a trader, a bounty hunter, hostile or a pirate STA NEWB ; and the escape pod we are about to spawn won't inherit ; any of these traits LDY #36 ; Update the NEWB flags in the ship's data block STA (INF),Y LDA #%00000000 ; Set the AI flag to 0 to disable AI, set aggression to STA INWK+32 ; zero and disable any E.C.M., so the ship's a sitting ; duck JMP SESCP ; Jump to SESCP to spawn an escape pod from the ship, ; returning from the subroutine using a tail call ; ****************************************************************************** ; ; Name: TACTICS (Part 5 of 7) ; Type: Subroutine ; Category: Tactics ; Summary: Apply tactics: Consider whether to launch a missile at us ; Deep dive: Program flow of the tactics routine ; ; ------------------------------------------------------------------------------ ; ; This section considers whether to launch a missile. Specifically: ; ; * If the ship doesn't have any missiles, skip to the next part ; ; * If an E.C.M. is firing, skip to the next part ; ; * Randomly decide whether to fire a missile (or, in the case of Thargoids, ; release a Thargon), and if we do, we're done ; ; ****************************************************************************** .ta3 ; If we get here then the ship has less than half energy ; so there may not be enough juice for lasers, but let's ; see if we can fire a missile LDA INWK+31 ; Set A = bits 0-2 of byte #31, the number of missiles AND #%00000111 ; the ship has left BEQ TA3 ; If it doesn't have any missiles, jump to TA3 STA T ; Store the number of missiles in T JSR DORND ; Set A and X to random numbers AND #31 ; Restrict A to a random number in the range 0-31 CMP T ; If A >= T, which is quite likely, though less likely BCS TA3 ; with higher numbers of missiles, jump to TA3 to skip ; firing a missile LDA ECMA ; If an E.C.M. is currently active (either ours or an BNE TA3 ; opponent's), jump to TA3 to skip firing a missile DEC INWK+31 ; We're done with the checks, so it's time to fire off a ; missile, so reduce the missile count in byte #31 by 1 LDA TYPE ; Fetch the ship type into A CMP #THG ; If this is not a Thargoid, jump down to TA16 to launch BNE TA16 ; a missile LDX #TGL ; This is a Thargoid, so instead of launching a missile, LDA INWK+32 ; the mothership launches a Thargon, so call SFS1 to JMP SFS1 ; spawn a Thargon from the parent ship, and return from ; the subroutine using a tail call .TA16 JMP SFRMIS ; Jump to SFRMIS to spawn a missile as a child of the ; current ship, make a noise and print a message warning ; of incoming missiles, and return from the subroutine ; using a tail call ; ****************************************************************************** ; ; Name: TACTICS (Part 6 of 7) ; Type: Subroutine ; Category: Tactics ; Summary: Apply tactics: Consider firing a laser at us, if aim is true ; Deep dive: Program flow of the tactics routine ; ; ------------------------------------------------------------------------------ ; ; This section looks at potentially firing the ship's laser at us. Specifically: ; ; * If the ship is not pointing at us, skip to the next part ; ; * If the ship is pointing at us but not accurately, fire its laser at us and ; skip to the next part ; ; * If we are in the ship's crosshairs, register some damage to our ship, slow ; down the attacking ship, make the noise of us being hit by laser fire, and ; we're done ; ; ****************************************************************************** .TA3 ; If we get here then the ship either has plenty of ; energy, or levels are low but it couldn't manage to ; launch a missile, so maybe we can fire the laser? LDA #0 ; Set A to x_hi OR y_hi OR z_hi JSR MAS4 AND #%11100000 ; If any of the hi bytes have any of bits 5-7 set, then BNE TA4 ; jump to TA4 to skip the laser checks, as the ship is ; too far away from us to hit us with a laser LDX CNT ; Set X = the dot product set above in CNT. If this is ; positive, this ship and our ship are facing in similar ; directions, but if it's negative then we are facing ; each other, so for us to be in the enemy ship's line ; of fire, X needs to be negative. The value in X can ; have a maximum magnitude of 36, which would mean we ; were facing each other square on, so in the following ; code we check X like this: ; ; X = 0 to -31, we are not in the enemy ship's line ; of fire, so they can't shoot at us ; ; X = -32 to -34, we are in the enemy ship's line ; of fire, so they can shoot at us, but they can't ; hit us as we're not dead in their crosshairs ; ; X = -35 to -36, we are bang in the middle of the ; enemy ship's crosshairs, so they can not only ; shoot at us, they can hit us ;BPL TA4 ; This instruction is commented out in the original ; source CPX #160 ; If X < 160, i.e. X > -32, then we are not in the enemy BCC TA4 ; ship's line of fire, so jump to TA4 to skip the laser ; checks LDY #19 ; Fetch the enemy ship's byte #19 from their ship's LDA (XX0),Y ; blueprint into A AND #%11111000 ; Extract bits 3-7, which contain the enemy's laser ; power BEQ TA4 ; If the enemy has no laser power, jump to TA4 to skip ; the laser checks LDA INWK+31 ; Set bit 6 in byte #31 to denote that the ship is ORA #%01000000 ; firing its laser at us STA INWK+31 CPX #163 ; If X < 163, i.e. X > -35, then we are not in the enemy BCC TA4 ; ship's crosshairs, so jump to TA4 to skip the laser ; checks ;LDY #19 ; This instruction is commented out in the original ; source LDA (XX0),Y ; Fetch the enemy ship's byte #19 from their ship's ; blueprint into A LSR A ; Halve the enemy ship's byte #19 (which contains both ; the laser power and number of missiles) to get the ; amount of damage we should take JSR OOPS ; Call OOPS to take some damage, which could do anything ; from reducing the shields and energy, all the way to ; losing cargo or dying (if the latter, we don't come ; back from this subroutine) DEC INWK+28 ; Halve the attacking ship's acceleration in byte #28 LDA ECMA ; If an E.C.M. is currently active (either ours or an BNE TA9-1 ; opponent's), return from the subroutine without making ; the laser-strike sound (as TA9-1 contains an RTS) LDY #sfxelas ; Call the NOISE routine with A = sfxelas to make the JSR NOISE ; first sound of us being hit by lasers LDY #sfxelas2 ; Call the NOISE routine with A = sfxelas2 to make the JMP NOISE ; second sound of us being hit by lasers, returning from ; the subroutine using a tail call ; ****************************************************************************** ; ; Name: TACTICS (Part 7 of 7) ; Type: Subroutine ; Category: Tactics ; Summary: Apply tactics: Set pitch, roll, and acceleration ; Deep dive: Program flow of the tactics routine ; Aggression and hostility in ship tactics ; ; ------------------------------------------------------------------------------ ; ; This section looks at manoeuvring the ship. Specifically: ; ; * Work out which direction the ship should be moving, depending on the type ; of ship, where it is, which direction it is pointing, and how aggressive ; it is ; ; * Set the pitch and roll counters to head in that direction ; ; * Speed up or slow down, depending on where the ship is in relation to us ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; TA151 Make the ship head towards the planet ; ; TA9-1 Contains an RTS ; ; ****************************************************************************** .TA4 LDA INWK+7 ; If z_hi >= 3 then the ship is quite far away, so jump CMP #3 ; down to TA5 BCS TA5 LDA INWK+1 ; Otherwise set A = x_hi OR y_hi and extract bits 1-7 ORA INWK+4 AND #%11111110 BEQ TA15 ; If A = 0 then the ship is pretty close to us, so jump ; to TA15 so it heads away from us .TA5 ; If we get here then the ship is quite far away JSR DORND ; Set A and X to random numbers ORA #%10000000 ; Set bit 7 of A, so the following comparison ignores ; the AI flag in bit 7 (as we already know bit 7 is set ; in byte #32) CMP INWK+32 ; If A >= byte #32 (the ship's AI flag) then jump down BCS TA15 ; to TA15 so it heads away from us ; We get here if byte #32 > A, where byte #32 is ; composed of the following: ; ; * Bit 7 set = AI is enabled ; ; * Bits 1-6 = aggression level (0 to 63) ; ; * Bit 0 set = ship has E.C.M. ; ; We set bit 7 of A above, so if we get here we know the ; ship has AI enabled, and the comparison then boils ; down to the following: ; ; Aggression level * 2 + E.C.M. > random number 0-127 ; ; In other words, higher aggression levels increase the ; chances of a ship changing direction to head towards ; us - or, to put it another way, ships with higher ; aggression levels are spoiling for a fight, with ; E.C.M. making them even more aggressive ; ; Thargoids and missiles both have an aggression level ; of 63 out of 63, which explains an awful lot ; ; Interestingly, escape pods also have a maximum ; agression level, but in this case it makes them fly ; towards the planet rather than towards us .TA20 ; If this is a missile we will have jumped straight ; here, but we also get here if the ship is either far ; away and aggressive, or not too close JSR TAS6 ; Call TAS6 to negate the vector in XX15 so it points in ; the opposite direction LDA CNT ; Change the sign of the dot product in CNT, so now it's EOR #%10000000 ; positive if the ships are facing each other, and ; negative if they are facing the same way .TA152 STA CNT ; Update CNT with the new value in A .TA15 ; If we get here, then one of the following is true: ; ; * This is a trader and XX15 is pointing towards the ; planet ; ; * The ship is pretty close to us, or it's just not ; very aggressive (though there is a random factor ; at play here too). XX15 is still pointing from our ; ship towards the enemy ship ; ; * The ship is aggressive (though again, there's an ; element of randomness here). XX15 is pointing from ; the enemy ship towards our ship ; ; * This is a missile heading for a target. XX15 is ; pointing from the missile towards the target ; ; We now want to move the ship in the direction of XX15, ; which will make aggressive ships head towards us, and ; ships that are too close turn away. Peaceful traders, ; meanwhile, head off towards the planet in search of a ; space station, and missiles home in on their targets LDY #16 ; Set (A X) = roofv . XX15 JSR TAS3 ; ; This will be positive if XX15 is pointing in the same ; direction as an arrow out of the top of the ship, in ; other words if the ship should pull up to head in the ; direction of XX15 TAX ; Copy A into X so we can retrieve it below EOR #%10000000 ; Give the ship's pitch counter the opposite sign to the AND #%10000000 ; dot product result, with a value of 0 STA INWK+30 TXA ; Retrieve the original value of A from X ASL A ; Shift A left to double it and drop the sign bit CMP RAT2 ; If A < RAT2, skip to TA11 (so if RAT2 = 0, we always BCC TA11 ; set the pitch counter to RAT) LDA RAT ; Set the magnitude of the ship's pitch counter to RAT ORA INWK+30 ; (we already set the sign above) STA INWK+30 .TA11 LDA INWK+29 ; Fetch the roll counter from byte #29 into A ASL A ; Shift A left to double it and drop the sign bit CMP #32 ; If A >= 32 then jump to TA6, as the ship is already BCS TA6 ; in the process of rolling LDY #22 ; Set (A X) = sidev . XX15 JSR TAS3 ; ; This will be positive if XX15 is pointing in the same ; direction as an arrow out of the right side of the ; ship, in other words if the ship should roll right to ; head in the direction of XX15 TAX ; Copy A into X so we can retrieve it below EOR INWK+30 ; Give the ship's roll counter a positive sign AND #%10000000 ; (clockwise roll) if the pitch counter and dot product EOR #%10000000 ; have different signs, negative (anti-clockwise roll) STA INWK+29 ; if they have the same sign, with a value of 0 TXA ; Retrieve the original value of A from X ASL A ; Shift A left to double it and drop the sign bit CMP RAT2 ; If A < RAT2, skip to TA12 (so if RAT2 = 0, we always BCC TA12 ; set the roll counter to RAT) LDA RAT ; Set the magnitude of the ship's roll counter to RAT ORA INWK+29 ; (we already set the sign above) STA INWK+29 .TA12 .TA6 LDA CNT ; Fetch the dot product, and if it's negative jump to BMI TA9 ; TA9, as the ships are facing away from each other and ; the ship might want to slow down to take another shot CMP CNT2 ; The dot product is positive, so the ships are facing BCC TA9 ; each other. If A < CNT2 then the ships are not heading ; directly towards each other, so jump to TA9 to slow ; down .PH10E LDA #3 ; Otherwise set the acceleration in byte #28 to 3 STA INWK+28 RTS ; Return from the subroutine .TA9 AND #%01111111 ; Clear the sign bit of the dot product in A CMP #18 ; If A < 18 then the ship is way off the XX15 vector, so BCC TA10 ; return from the subroutine (TA10 contains an RTS) ; without slowing down, as it still has quite a bit of ; turning to do to get on course LDA #$FF ; Otherwise set A = -1 LDX TYPE ; If this is not a missile then skip the ASL instruction CPX #MSL BNE P%+3 ASL A ; This is a missile, so set A = -2, as missiles are more ; nimble and can brake more quickly STA INWK+28 ; Set the ship's acceleration to A .TA10 RTS ; Return from the subroutine .TA151 ; This is called from part 3 with the vector to the ; planet in XX15, when we want the ship to turn towards ; the planet. It does the same dot product calculation ; as part 3, but it can also change the value of RAT2 ; so that roll and pitch is always applied LDY #10 ; Set (A X) = nosev . XX15 JSR TAS3 ; ; The bigger the value of the dot product, the more ; aligned the two vectors are, with a maximum magnitude ; in A of 36 (96 * 96 >> 8). If A is positive, the ; vectors are facing in a similar direction, if it's ; negative they are facing in opposite directions CMP #$98 ; If A is positive or A <= -24, jump to ttt BCC ttt LDX #0 ; A > -24, which means the vectors are facing in STX RAT2 ; opposite directions but are quite aligned, so set ; RAT2 = 0 instead of the default value of 4, so we ; always apply roll and pitch when we turn the ship ; all the way around towards the planet .ttt JMP TA152 ; Jump to TA152 to store A in CNT and move the ship in ; the direction of XX15 ; ****************************************************************************** ; ; Name: DOCKIT ; Type: Subroutine ; Category: Flight ; Summary: Apply docking manoeuvres to the ship in INWK ; Deep dive: The docking computer ; ; ****************************************************************************** .DOCKIT LDA #6 ; Set RAT2 = 6, which is the threshold below which we STA RAT2 ; don't apply pitch and roll to the ship (so a lower ; value means we apply pitch and roll more often, and a ; value of 0 means we always apply them). The value is ; compared with double the high byte of sidev . XX15, ; where XX15 is the vector from the ship to the station LSR A ; Set RAT = 2, which is the magnitude we set the pitch STA RAT ; or roll counter to in part 7 when turning a ship ; towards a vector (a higher value giving a longer ; turn) LDA #29 ; Set CNT2 = 29, which is the maximum angle beyond which STA CNT2 ; a ship will slow down to start turning towards its ; prey (a lower value means a ship will start to slow ; down even if its angle with the enemy ship is large, ; which gives a tighter turn) LDA SSPR ; If we are inside the space station safe zone, skip the BNE P%+5 ; next instruction .GOPLS JMP GOPL ; Jump to GOPL to make the ship head towards the planet JSR VCSU1 ; If we get here then we are in the space station safe ; zone, so call VCSU1 to calculate the following, where ; the station is at coordinates (station_x, station_y, ; station_z): ; ; K3(2 1 0) = (x_sign x_hi x_lo) - station_x ; ; K3(5 4 3) = (y_sign y_hi z_lo) - station_y ; ; K3(8 7 6) = (z_sign z_hi z_lo) - station_z ; ; so K3 contains the vector from the station to the ship LDA K3+2 ; If any of the top bytes of the K3 results above are ORA K3+5 ; non-zero (after removing the sign bits), jump to GOPL ORA K3+8 ; via GOPLS to make the ship head towards the planet, as AND #%01111111 ; this will aim the ship in the general direction of the BNE GOPLS ; station (it's too far away for anything more accurate) JSR TA2 ; Call TA2 to calculate the length of the vector in K3 ; (ignoring the low coordinates), returning it in Q LDA Q ; Store the value of Q in K, so K now contains the STA K ; distance between station and the ship JSR TAS2 ; Call TAS2 to normalise the vector in K3, returning the ; normalised version in XX15, so XX15 contains the unit ; vector pointing from the station to the ship LDY #10 ; Call TAS4 to calculate: JSR TAS4 ; ; (A X) = nosev . XX15 ; ; where nosev is the nose vector of the space station, ; so this is the dot product of the station to ship ; vector with the station's nosev (which points straight ; out into space, out of the docking slot), and because ; both vectors are unit vectors, the following is also ; true: ; ; (A X) = cos(t) ; ; where t is the angle between the two vectors ; ; If the dot product is positive, that means the vector ; from the station to the ship and the nosev sticking ; out of the docking slot are facing in a broadly ; similar direction (so the ship is essentially heading ; for the slot, which is facing towards the ship), and ; if it's negative they are facing in broadly opposite ; directions (so the station slot is on the opposite ; side of the station as the ship approaches) BMI PH1 ; If the dot product is negative, i.e. the station slot ; is on the opposite side, jump to PH1 to fly towards ; the ideal docking position, some way in front of the ; slot CMP #35 ; If the dot product < 35, jump to PH1 to fly towards BCC PH1 ; the ideal docking position, some way in front of the ; slot, as there is a large angle between the vector ; from the station to the ship and the station's nosev, ; so the angle of approach is not very optimal ; ; Specifically, as the unit vector length is 96 in our ; vector system, ; ; (A X) = cos(t) < 35 / 96 ; ; so: ; ; t > arccos(35 / 96) = 68.6 degrees ; ; so the ship is coming in from the side of the station ; at an angle between 68.6 and 90 degrees off the ; optimal entry angle ; If we get here, the slot is on the same side as the ; ship and the angle of approach is less than 68.6 ; degrees, so we're heading in pretty much the correct ; direction for a good approach to the docking slot LDY #10 ; Call TAS3 to calculate: JSR TAS3 ; ; (A X) = nosev . XX15 ; ; where nosev is the nose vector of the ship, so this is ; the dot product of the station to ship vector with the ; ship's nosev, and is a measure of how close to the ; station the ship is pointing, with negative meaning it ; is pointing at the station, and positive meaning it is ; pointing away from the station CMP #$A2 ; If the dot product is in the range 0 to -34, jump to BCS PH3 ; PH3 to refine our approach, as we are pointing towards ; the station ; If we get here, then we are not pointing straight at ; the station, so check how close we are LDA K ; Fetch the distance to the station into A ;BEQ PH10 ; This instruction is commented out in the original ; source CMP #157 ; If A < 157, jump to PH2 to turn away from the station, BCC PH2 ; as we are too close LDA TYPE ; Fetch the ship type into A BMI PH3 ; If bit 7 is set, then that means the ship type was set ; to -96 in the DOKEY routine when we switched on our ; docking computer, so this is us auto-docking our ; Cobra, so jump to PH3 to refine our approach ; ; Otherwise this is an NPC trying to dock, so keep going ; to turn away from the station .PH2 ; If we get here then we turn away from the station and ; slow right down, effectively aborting this approach ; attempt JSR TAS6 ; Call TAS6 to negate the vector in XX15 so it points in ; the opposite direction, away from the station and ; towards the ship JSR TA151 ; Call TA151 to make the ship head in the direction of ; XX15, which makes the ship turn away from the station .PH22 ; If we get here then we slam on the brakes and slow ; right down LDX #0 ; Set the acceleration in byte #28 to 0 STX INWK+28 INX ; Set the speed in byte #28 to 1 STX INWK+27 RTS ; Return from the subroutine .PH1 ; If we get here then the slot is on the opposite side ; of the station to the ship, or it's on the same side ; and the approach angle is not optimal, so we just fly ; towards the station, aiming for the ideal docking ; position some distance in front of the slot JSR VCSU1 ; Call VCSU1 to set K3 to the vector from the station to ; the ship JSR DCS1 ; Call DCS1 twice to calculate the vector from the ideal JSR DCS1 ; docking position to the ship, where the ideal docking ; position is straight out of the docking slot at a ; distance of 8 unit vectors from the centre of the ; station JSR TAS2 ; Call TAS2 to normalise the vector in K3, returning the ; normalised version in XX15 JSR TAS6 ; Call TAS6 to negate the vector in XX15 so it points in ; the opposite direction JMP TA151 ; Call TA151 to make the ship head in the direction of ; XX15, which makes the ship turn towards the ideal ; docking position, and return from the subroutine using ; a tail call .TN11 ; If we get here, we accelerate and apply a full ; clockwise roll (which matches the space station's ; roll) INC INWK+28 ; Increment the acceleration in byte #28 LDA #%01111111 ; Set the roll counter to a positive (clockwise) roll STA INWK+29 ; with no damping, to match the space station's roll BNE TN13 ; Jump down to TN13 (this BNE is effectively a JMP as ; A will never be zero) .PH3 ; If we get here, we refine our approach using pitch and ; roll to aim for the station LDX #0 ; Set RAT2 = 0 so we always apply roll and pitch when we STX RAT2 ; refine the ship's approach STX INWK+30 ; Set the pitch counter to 0 to stop any pitching LDA TYPE ; If this is not our ship's docking computer, but is an BPL PH32 ; NPC ship trying to dock, jump to PH32 ; In the following, ship_x and ship_y are the x and ; y-coordinates of XX15, the vector from the station to ; the ship EOR XX15 ; A is negative, so this sets the sign of A to the same EOR XX15+1 ; as -XX15 * XX15+1, or -ship_x * ship_y ASL A ; Shift the sign bit into the C flag, so the C flag has ; the following sign: ; ; * Positive if ship_x and ship_y have different signs ; * Negative if ship_x and ship_y have the same sign LDA #2 ; Set A = +2 or -2, giving it the sign in the C flag, ROR A ; and store it in byte #29, the roll counter, so that STA INWK+29 ; the ship rolls towards the station LDA XX15 ; If |ship_x * 2| >= 12, i.e. |ship_x| >= 6, then jump ASL A ; to PH22 to slow right down and return from the CMP #12 ; subroutine, as the station is not in our sights BCS PH22 LDA XX15+1 ; Set A = +2 or -2, giving it the same sign as ship_y, ASL A ; and store it in byte #30, the pitch counter, so that LDA #2 ; the ship pitches towards the station ROR A STA INWK+30 LDA XX15+1 ; If |ship_y * 2| >= 12, i.e. |ship_y| >= 6, then jump ASL A ; to PH22 to slow right down and return from the CMP #12 ; subroutine, as the station is not in our sights BCS PH22 .PH32 ; If we get here, we try to match the station roll STX INWK+29 ; Set the roll counter to 0 to stop any pitching LDA INWK+22 ; Set XX15 = sidev_x_hi STA XX15 LDA INWK+24 ; Set XX15+1 = sidev_y_hi STA XX15+1 LDA INWK+26 ; Set XX15+2 = sidev_z_hi STA XX15+2 ; ; so XX15 contains the sidev vector of the ship LDY #16 ; Call TAS4 to calculate: JSR TAS4 ; ; (A X) = roofv . XX15 ; ; where roofv is the roof vector of the space station. ; To dock with the slot horizontal, we want roofv to be ; pointing off to the side, i.e. parallel to the ship's ; sidev vector, which means we want the dot product to ; be large (it can be positive or negative, as roofv can ; point left or right - it just needs to be parallel to ; the ship's sidev) ASL A ; If |A * 2| >= 66, i.e. |A| >= 33, then the ship is CMP #66 ; lined up with the slot, so jump to TN11 to accelerate BCS TN11 ; and roll clockwise (a positive roll) before jumping ; down to TN13 to check if we're docked yet JSR PH22 ; Call PH22 to slow right down, as we haven't yet ; matched the station's roll .TN13 ; If we get here, we check to see if we have docked LDA K3+10 ; If K3+10 is non-zero, skip to TNRTS, to return from BNE TNRTS ; the subroutine ; ; I have to say I have no idea what K3+10 contains, as ; it isn't mentioned anywhere in the whole codebase ; apart from here, but it does share a location with ; XX2+10, so it will sometimes be non-zero (specifically ; when face #10 in the ship we're drawing is visible, ; which probably happens quite a lot). This would seem ; to affect whether an NPC ship can dock, as that's the ; code that gets skipped if K3+10 is non-zero, but as ; to what this means... that's not yet clear ASL NEWB ; Set bit 7 of the ship's NEWB flags to indicate that SEC ; the ship has now docked, which only has meaning if ROR NEWB ; this is an NPC trying to dock .TNRTS RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: VCSU1 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate vector K3(8 0) = [x y z] - coordinates of the sun or ; space station ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; K3(2 1 0) = (x_sign x_hi x_lo) - x-coordinate of the sun or space station ; ; K3(5 4 3) = (y_sign y_hi z_lo) - y-coordinate of the sun or space station ; ; K3(8 7 6) = (z_sign z_hi z_lo) - z-coordinate of the sun or space station ; ; where the first coordinate is from the ship data block in INWK, and the second ; coordinate is from the sun or space station's ship data block which they ; share. ; ; ****************************************************************************** .VCSU1 LDA #LO(K%+NI%) ; Set the low byte of V(1 0) to point to the coordinates STA V ; of the sun or space station LDA #HI(K%+NI%) ; Set A to the high byte of the address of the ; coordinates of the sun or space station ; Fall through into VCSUB to calculate: ; ; K3(2 1 0) = (x_sign x_hi x_lo) - x-coordinate of sun ; or space station ; ; K3(2 1 0) = (x_sign x_hi x_lo) - x-coordinate of sun ; or space station ; ; K3(8 7 6) = (z_sign z_hi z_lo) - z-coordinate of sun ; or space station ; ****************************************************************************** ; ; Name: VCSUB ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate vector K3(8 0) = [x y z] - coordinates in (A V) ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; K3(2 1 0) = (x_sign x_hi x_lo) - x-coordinate in (A V) ; ; K3(5 4 3) = (y_sign y_hi z_lo) - y-coordinate in (A V) ; ; K3(8 7 6) = (z_sign z_hi z_lo) - z-coordinate in (A V) ; ; where the first coordinate is from the ship data block in INWK, and the second ; coordinate is from the ship data block pointed to by (A V). ; ; ****************************************************************************** .VCSUB STA V+1 ; Set the low byte of V(1 0) to A, so now V(1 0) = (A V) LDY #2 ; K3(2 1 0) = (x_sign x_hi x_lo) - x-coordinate in data JSR TAS1 ; block at V(1 0) LDY #5 ; K3(5 4 3) = (y_sign y_hi z_lo) - y-coordinate of data JSR TAS1 ; block at V(1 0) LDY #8 ; Fall through into TAS1 to calculate the final result: ; ; K3(8 7 6) = (z_sign z_hi z_lo) - z-coordinate of data ; block at V(1 0) ; ****************************************************************************** ; ; Name: TAS1 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate K3 = (x_sign x_hi x_lo) - V(1 0) ; ; ------------------------------------------------------------------------------ ; ; Calculate one of the following, depending on the value in Y: ; ; K3(2 1 0) = (x_sign x_hi x_lo) - x-coordinate in V(1 0) ; ; K3(5 4 3) = (y_sign y_hi z_lo) - y-coordinate in V(1 0) ; ; K3(8 7 6) = (z_sign z_hi z_lo) - z-coordinate in V(1 0) ; ; where the first coordinate is from the ship data block in INWK, and the second ; coordinate is from the ship data block pointed to by V(1 0). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; V(1 0) The address of the ship data block to subtract ; ; Y The coordinate in the V(1 0) block to subtract: ; ; * If Y = 2, subtract the x-coordinate and store the ; result in K3(2 1 0) ; ; * If Y = 5, subtract the y-coordinate and store the ; result in K3(5 4 3) ; ; * If Y = 8, subtract the z-coordinate and store the ; result in K3(8 7 6) ; ; ****************************************************************************** .TAS1 LDA (V),Y ; Copy the sign byte of the V(1 0) coordinate into K+3, EOR #%10000000 ; flipping it in the process STA K+3 DEY ; Copy the high byte of the V(1 0) coordinate into K+2 LDA (V),Y STA K+2 DEY ; Copy the high byte of the V(1 0) coordinate into K+1, LDA (V),Y ; so now: STA K+1 ; ; K(3 2 1) = - coordinate in V(1 0) STY U ; Copy the index (now 0, 3 or 6) into U and X LDX U JSR MVT3 ; Call MVT3 to add the same coordinates, but this time ; from INWK, so this would look like this for the ; x-axis: ; ; K(3 2 1) = (x_sign x_hi x_lo) + K(3 2 1) ; = (x_sign x_hi x_lo) - coordinate in V(1 0) LDY U ; Restore the index into Y, though this instruction has ; no effect, as Y is not used again, either here or ; following calls to this routine STA K3+2,X ; Store K(3 2 1) in K3+X(2 1 0), starting with the sign ; byte LDA K+2 ; And then doing the high byte STA K3+1,X LDA K+1 ; And finally the low byte STA K3,X RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TAS4 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Calculate the dot product of XX15 and one of the space station's ; orientation vectors ; ; ------------------------------------------------------------------------------ ; ; Calculate the dot product of the vector in XX15 and one of the space station's ; orientation vectors, as determined by the value of Y. If vect is the space ; station orientation vector, we calculate this: ; ; (A X) = vect . XX15 ; = vect_x * XX15 + vect_y * XX15+1 + vect_z * XX15+2 ; ; Technically speaking, this routine can also calculate the dot product between ; XX15 and the sun's orientation vectors, as the sun and space station share the ; same ship data slot (the second ship data block at K%). However, the sun ; doesn't have orientation vectors, so this only gets called when that slot is ; being used for the space station. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The space station's orientation vector: ; ; * If Y = 10, calculate nosev . XX15 ; ; * If Y = 16, calculate roofv . XX15 ; ; * If Y = 22, calculate sidev . XX15 ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; (A X) The result of the dot product ; ; ****************************************************************************** .TAS4 LDX K%+NI%,Y ; Set Q = the Y-th byte of K%+NI%, i.e. vect_x from the STX Q ; second ship data block at K% LDA XX15 ; Set A = XX15 JSR MULT12 ; Set (S R) = Q * A ; = vect_x * XX15 LDX K%+NI%+2,Y ; Set Q = the Y+2-th byte of K%+NI%, i.e. vect_y STX Q LDA XX15+1 ; Set A = XX15+1 JSR MAD ; Set (A X) = Q * A + (S R) ; = vect_y * XX15+1 + vect_x * XX15 STA S ; Set (S R) = (A X) STX R LDX K%+NI%+4,Y ; Set Q = the Y+4-th byte of K%+NI%, i.e. vect_z STX Q LDA XX15+2 ; Set A = XX15+2 JMP MAD ; Set: ; ; (A X) = Q * A + (S R) ; = vect_z * XX15+2 + vect_y * XX15+1 + ; vect_x * XX15 ; ; and return from the subroutine using a tail call ; ****************************************************************************** ; ; Name: TAS6 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Negate the vector in XX15 so it points in the opposite direction ; ; ****************************************************************************** .TAS6 LDA XX15 ; Reverse the sign of the x-coordinate of the vector in EOR #%10000000 ; XX15 STA XX15 LDA XX15+1 ; Then reverse the sign of the y-coordinate EOR #%10000000 STA XX15+1 LDA XX15+2 ; And then the z-coordinate, so now the XX15 vector is EOR #%10000000 ; pointing in the opposite direction STA XX15+2 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DCS1 ; Type: Subroutine ; Category: Flight ; Summary: Calculate the vector from the ideal docking position to the ship ; ; ------------------------------------------------------------------------------ ; ; This routine is called by the docking computer routine in DOCKIT. It works out ; the vector between the ship and the ideal docking position, which is straight ; in front of the docking slot, but some distance away. ; ; Specifically, it calculates the following: ; ; * K3(2 1 0) = K3(2 1 0) - nosev_x_hi * 4 ; ; * K3(5 4 3) = K3(5 4 3) - nosev_y_hi * 4 ; ; * K3(8 7 6) = K3(8 7 6) - nosev_x_hi * 4 ; ; where K3 is the vector from the station to the ship, and nosev is the nose ; vector for the space station. ; ; The nose vector points from the centre of the station through the slot, so ; -nosev * 4 is the vector from a point in front of the docking slot, but some ; way from the station, back to the centre of the station. Adding this to the ; vector from the station to the ship gives the vector from the point in front ; of the station to the ship. ; ; In practice, this routine is called twice, so the ideal docking position is ; actually at a distance of 8 unit vectors from the centre of the station. ; ; Back in DOCKIT, we flip this vector round to get the vector from the ship to ; the point in front of the station slot. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; K3 The vector from the station to the ship ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; K3 The vector from the ship to the ideal docking position ; (4 unit vectors from the centre of the station for each ; call to DCS1, so two calls will return the vector to a ; point that's 8 unit vectors from the centre of the ; station) ; ; ****************************************************************************** .DCS1 JSR P%+3 ; Run the following routine twice, so the subtractions ; are all * 4 LDA K%+NI%+10 ; Set A to the space station's byte #10, nosev_x_hi LDX #0 ; Set K3(2 1 0) = K3(2 1 0) - A * 2 JSR TAS7 ; = K3(2 1 0) - nosev_x_hi * 2 LDA K%+NI%+12 ; Set A to the space station's byte #12, nosev_y_hi LDX #3 ; Set K3(5 4 3) = K3(5 4 3) - A * 2 JSR TAS7 ; = K3(5 4 3) - nosev_y_hi * 2 LDA K%+NI%+14 ; Set A to the space station's byte #14, nosev_z_hi LDX #6 ; Set K3(8 7 6) = K3(8 7 6) - A * 2 ; = K3(8 7 6) - nosev_x_hi * 2 .TAS7 ; This routine subtracts A * 2 from one of the K3 ; coordinates, as determined by the value of X: ; ; * X = 0, set K3(2 1 0) = K3(2 1 0) - A * 2 ; ; * X = 3, set K3(5 4 3) = K3(5 4 3) - A * 2 ; ; * X = 6, set K3(8 7 6) = K3(8 7 6) - A * 2 ; ; Let's document it for X = 0, i.e. K3(2 1 0) ASL A ; Shift A left one place and move the sign bit into the ; C flag, so A = |A * 2| STA R ; Set R = |A * 2| LDA #0 ; Rotate the sign bit of A from the C flag into the sign ROR A ; bit of A, so A is now just the sign bit from the ; original value of A. This also clears the C flag EOR #%10000000 ; Flip the sign bit of A, so it has the sign of -A EOR K3+2,X ; Give A the correct sign of K3(2 1 0) * -A BMI TS71 ; If the sign of K3(2 1 0) * -A is negative, jump to ; TS71, as K3(2 1 0) and A have the same sign ; If we get here then K3(2 1 0) and A have different ; signs, so we can add them to do the subtraction LDA R ; Set K3(2 1 0) = K3(2 1 0) + R ADC K3,X ; = K3(2 1 0) + |A * 2| STA K3,X ; ; starting with the low bytes BCC TS72 ; If the above addition didn't overflow, we have the ; result we want, so jump to TS72 to return from the ; subroutine INC K3+1,X ; The above addition overflowed, so increment the high ; byte of K3(2 1 0) .TS72 RTS ; Return from the subroutine .TS71 ; If we get here, then K3(2 1 0) and A have the same ; sign LDA K3,X ; Set K3(2 1 0) = K3(2 1 0) - R SEC ; = K3(2 1 0) - |A * 2| SBC R ; STA K3,X ; starting with the low bytes LDA K3+1,X ; And then the high bytes SBC #0 STA K3+1,X BCS TS72 ; If the subtraction didn't underflow, we have the ; result we want, so jump to TS72 to return from the ; subroutine LDA K3,X ; Negate the result in K3(2 1 0) by flipping all the EOR #%11111111 ; bits and adding 1, i.e. using two's complement to ADC #1 ; give it the opposite sign, starting with the low STA K3,X ; bytes LDA K3+1,X ; Then doing the high bytes EOR #%11111111 ADC #0 STA K3+1,X LDA K3+2,X ; And finally, flipping the sign bit EOR #%10000000 STA K3+2,X JMP TS72 ; Jump to TS72 to return from the subroutine ; ****************************************************************************** ; ; Name: HITCH ; Type: Subroutine ; Category: Tactics ; Summary: Work out if the ship in INWK is in our crosshairs ; Deep dive: In the crosshairs ; ; ------------------------------------------------------------------------------ ; ; This is called by the main flight loop to see if we have laser or missile lock ; on an enemy ship. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if the ship is in our crosshairs, clear if it isn't ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; HI1 Contains an RTS ; ; ****************************************************************************** .HITCH CLC ; Clear the C flag so we can return with it cleared if ; our checks fail LDA INWK+8 ; Set A = z_sign BNE HI1 ; If A is non-zero then the ship is behind us and can't ; be in our crosshairs, so return from the subroutine ; with the C flag clear (as HI1 contains an RTS) LDA TYPE ; If the ship type has bit 7 set then it is the planet BMI HI1 ; or sun, which we can't target or hit with lasers, so ; return from the subroutine with the C flag clear (as ; HI1 contains an RTS) LDA INWK+31 ; Fetch bit 5 of byte #31 (the exploding flag) and OR AND #%00100000 ; with x_hi and y_hi ORA INWK+1 ORA INWK+4 BNE HI1 ; If this value is non-zero then either the ship is ; exploding (so we can't target it), or the ship is too ; far away from our line of fire to be targeted, so ; return from the subroutine with the C flag clear (as ; HI1 contains an RTS) LDA INWK ; Set A = x_lo JSR SQUA2 ; Set (A P) = A * A = x_lo^2 STA S ; Set (S R) = (A P) = x_lo^2 LDA P STA R LDA INWK+3 ; Set A = y_lo JSR SQUA2 ; Set (A P) = A * A = y_lo^2 TAX ; Store the high byte in X LDA P ; Add the two low bytes, so: ADC R ; STA R ; R = P + R TXA ; Restore the high byte into A and add S to give the ADC S ; following: ; ; (A R) = (S R) + (A P) = x_lo^2 + y_lo^2 BCS TN10 ; If the addition just overflowed then there is no way ; our crosshairs are within the ship's targetable area, ; so return from the subroutine with the C flag clear ; (as TN10 contains a CLC then an RTS) STA S ; Set (S R) = (A P) = x_lo^2 + y_lo^2 LDY #2 ; Fetch the ship's blueprint and set A to the high byte LDA (XX0),Y ; of the targetable area of the ship CMP S ; We now compare the high bytes of the targetable area ; and the calculation in (S R): ; ; * If A >= S then then the C flag will be set ; ; * If A < S then the C flag will be C clear BNE HI1 ; If A <> S we have just set the C flag correctly, so ; return from the subroutine (as HI1 contains an RTS) DEY ; The high bytes were identical, so now we fetch the LDA (XX0),Y ; low byte of the targetable area into A CMP R ; We now compare the low bytes of the targetable area ; and the calculation in (S R): ; ; * If A >= R then the C flag will be set ; ; * If A < R then the C flag will be C clear .HI1 RTS ; Return from the subroutine .TN10 CLC ; Clear the C flag to indicate the ship is not in our ; crosshairs RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: FRS1 ; Type: Subroutine ; Category: Tactics ; Summary: Launch a ship straight ahead of us, below the laser sights ; ; ------------------------------------------------------------------------------ ; ; This is used in three places: ; ; * When we launch a missile, in which case the missile is the ship that is ; launched ahead of us ; ; * When we launch our escape pod, in which case it's our abandoned Cobra Mk ; III that is launched ahead of us ; ; * The fq1 entry point is used to launch a bunch of cargo canisters ahead of ; us as part of the death screen ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The type of ship to launch ahead of us ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if the ship was successfully launched, clear if it ; wasn't (as there wasn't enough free memory) ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; fq1 Used to add a cargo canister to the universe ; ; ****************************************************************************** .FRS1 JSR ZINF ; Call ZINF to reset the INWK ship workspace LDA #28 ; Set y_lo = 28 STA INWK+3 LSR A ; Set z_lo = 14, so the launched ship starts out STA INWK+6 ; ahead of us LDA #%10000000 ; Set y_sign to be negative, so the launched ship is STA INWK+5 ; launched just below our line of sight LDA MSTG ; Set A to the missile lock target, shifted left so the ASL A ; slot number is in bits 1-5 ORA #%10000000 ; Set bit 7 and store the result in byte #32, the AI STA INWK+32 ; flag launched ship for the launched ship. For missiles ; this enables AI (bit 7), makes it friendly towards us ; (bit 6), sets the target to the value of MSTG (bits ; 1-5), and sets its lock status as launched (bit 0). ; It doesn't matter what it does for our abandoned ; Cobra, as the AI flag gets overwritten once we return ; from the subroutine back to the ESCAPE routine that ; called FRS1 in the first place .fq1 LDA #$60 ; Set byte #14 (nosev_z_hi) to 1 ($60), so the launched STA INWK+14 ; ship is pointing away from us ORA #128 ; Set byte #22 (sidev_x_hi) to -1 ($D0), so the launched STA INWK+22 ; ship has the same orientation as spawned ships, just ; pointing away from us (if we set sidev to +1 instead, ; this ship would be a mirror image of all the other ; ships, which are spawned with -1 in nosev and +1 in ; sidev) LDA DELTA ; Set byte #27 (speed) to 2 * DELTA, so the launched ROL A ; ship flies off at twice our speed STA INWK+27 TXA ; Add a new ship of type X to our local bubble of JMP NWSHP ; universe and return from the subroutine using a tail ; call ; ****************************************************************************** ; ; Name: FRMIS ; Type: Subroutine ; Category: Tactics ; Summary: Fire a missile from our ship ; ; ------------------------------------------------------------------------------ ; ; We fired a missile, so send it streaking away from us to unleash mayhem and ; destruction on our sworn enemies. ; ; ****************************************************************************** .FRMIS LDX #MSL ; Call FRS1 to launch a missile straight ahead of us JSR FRS1 BCC FR1 ; If FRS1 returns with the C flag clear, then there ; isn't room in the universe for our missile, so jump ; down to FR1 to display a "missile jammed" message LDX MSTG ; Fetch the slot number of the missile's target JSR GINF ; Get the address of the data block for the target ship ; and store it in INF LDA FRIN,X ; Fetch the ship type of the missile's target into A JSR ANGRY ; Call ANGRY to make the target ship or station hostile, ; and if this is a ship, wake up its AI and give it a ; kick of speed LDY #BLACK2 ; We have just launched a missile, so we need to remove JSR ABORT ; missile lock and hide the leftmost indicator on the ; dashboard by setting it to black DEC NOMSL ; Reduce the number of missiles we have by 1 LDY #sfxwhosh ; Call the NOISE routine with Y = sfxwhosh to make the JMP NOISE ; sound of a missile launch, returning from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: ANGRY ; Type: Subroutine ; Category: Tactics ; Summary: Make a ship or station hostile, and if this is a ship then enable ; the ship's AI and give it a kick of speed ; Deep dive: Aggression and hostility in ship tactics ; ; ------------------------------------------------------------------------------ ; ; This routine makes a ship or station angry by setting the hostile flag in ; NEWB, and for ships it also means enabling the ship's AI and giving it a kick ; of turning acceleration. Later calls to TACTICS may make the ship start to ; attack us if it has a high enough aggression level. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The type of ship we're going to irritate ; ; INF The address of the data block for the ship we're going ; to infuriate ; ; ****************************************************************************** .ANGRY CMP #SST ; If this is the space station, jump to AN2 to make the BEQ AN2 ; space station hostile LDY #36 ; Fetch the ship's NEWB flags from byte #36 LDA (INF),Y AND #%00100000 ; If bit 5 of the ship's NEWB flags is clear, skip the BEQ P%+5 ; following instruction, otherwise bit 5 is set, meaning ; this ship is an innocent bystander, and attacking it ; will annoy the space station JSR AN2 ; Call AN2 to make the space station hostile LDY #32 ; Fetch the ship's byte #32 (AI flag) LDA (INF),Y BEQ HI1 ; If the AI flag is zero then this ship has no AI and ; zero aggression, so return from the subroutine (as ; HI1 contains an RTS) ORA #%10000000 ; Otherwise set bit 7 (AI enabled) to ensure AI is STA (INF),Y ; definitely enabled, so the ship can start acting ; according to its aggression level LDY #28 ; Set the ship's byte #28 (acceleration) to 2, so it LDA #2 ; speeds up STA (INF),Y ASL A ; Set the ship's byte #30 (pitch counter) to 4, so it LDY #30 ; starts diving STA (INF),Y LDA TYPE ; If the ship's type is < #CYL (i.e. a missile, Coriolis CMP #CYL ; space station, escape pod, plate, cargo canister, BCC AN3 ; boulder, asteroid, splinter, Shuttle or Transporter), ; then jump to AN3 to skip the following LDY #36 ; Set bit 2 of the ship's NEWB flags in byte #36 to LDA (INF),Y ; make this ship hostile ORA #%00000100 STA (INF),Y .AN3 RTS ; Return from the subroutine .AN2 LDA K%+NI%+36 ; Set bit 2 of the NEWB flags in byte #36 of the second ORA #%00000100 ; ship in the ship data workspace at K%, which is STA K%+NI%+36 ; reserved for the sun or the space station (in this ; case it's the latter), to make it hostile RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: FR1 ; Type: Subroutine ; Category: Tactics ; Summary: Display the "missile jammed" message ; ; ------------------------------------------------------------------------------ ; ; This is shown if there isn't room in the local bubble of universe for a new ; missile. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; FR1-2 Clear the C flag and return from the subroutine ; ; ****************************************************************************** .FR1 LDA #201 ; Print recursive token 41 ("MISSILE JAMMED") as an JMP MESS ; in-flight message and return from the subroutine using ; a tail call ; ****************************************************************************** ; ; Name: SESCP ; Type: Subroutine ; Category: Flight ; Summary: Spawn an escape pod from the current (parent) ship ; Deep dive: Aggression and hostility in ship tactics ; ; ------------------------------------------------------------------------------ ; ; This is called when an enemy ship has run out of both energy and luck, so it's ; time to bail. ; ; ****************************************************************************** .SESCP LDX #ESC ; Set X to the ship type for an escape pod LDA #%11111110 ; Set A to use as an AI flag that has AI enabled, an ; aggression level of 63 out of 63, and no E.C.M. ; ; When spawning an escape pod, this high agression level ; makes the pod turn towards the planet rather than ; towards us ; ; This instruction is also used as an entry point to ; spawn missile (when calling via the SFS1-2 entry ; point), in which case the missile has AI (bit 7 set), ; is hostile (bit 6 set) and has been launched (bit 0 ; clear); the target slot number is set to 31, but this ; is ignored as the hostile flag means we are the target ; Fall through into SFS1 to spawn the escape pod or ; missile ; ****************************************************************************** ; ; Name: SFS1 ; Type: Subroutine ; Category: Universe ; Summary: Spawn a child ship from the current (parent) ship ; ; ------------------------------------------------------------------------------ ; ; If the parent is a space station then the child ship is spawned coming out of ; the slot, and if the child is a cargo canister, it is sent tumbling through ; space. Otherwise the child ship is spawned with the same ship data as the ; parent, just with damping disabled and the ship type and AI flag that are ; passed in A and X. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A AI flag for the new ship (see the documentation on ship ; data byte #32 for details) ; ; X The ship type of the child to spawn ; ; INF Address of the parent's ship data block ; ; TYPE The type of the parent ship ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if ship successfully added, clear if it failed ; ; INF INF is preserved ; ; XX0 XX0 is preserved ; ; INWK The whole INWK workspace is preserved ; ; X X is preserved ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; SFS1-2 Used to add a missile to the local bubble that that has ; AI (bit 7 set), is hostile (bit 6 set) and has been ; launched (bit 0 clear); the target slot number is set to ; 31, but this is ignored as the hostile flags means we ; are the target ; ; ****************************************************************************** .SFS1 STA T1 ; Store the child ship's AI flag in T1 ; Before spawning our child ship, we need to save the ; INF and XX00 variables and the whole INWK workspace, ; so we can restore them later when returning from the ; subroutine TXA ; Store X, the ship type to spawn, on the stack so we PHA ; can preserve it through the routine LDA XX0 ; Store XX0(1 0) on the stack, so we can restore it PHA ; later when returning from the subroutine LDA XX0+1 PHA LDA INF ; Store INF(1 0) on the stack, so we can restore it PHA ; later when returning from the subroutine LDA INF+1 PHA LDY #NI%-1 ; Now we want to store the current INWK data block in ; temporary memory so we can restore it when we are ; done, and we also want to copy the parent's ship data ; into INWK, which we can do at the same time, so set up ; a counter in Y for NI% bytes .FRL2 LDA INWK,Y ; Copy the Y-th byte of INWK to the Y-th byte of STA XX3,Y ; temporary memory in XX3, so we can restore it later ; when returning from the subroutine LDA (INF),Y ; Copy the Y-th byte of the parent ship's data block to STA INWK,Y ; the Y-th byte of INWK DEY ; Decrement the loop counter BPL FRL2 ; Loop back to copy the next byte until we have done ; them all ; INWK now contains the ship data for the parent ship, ; so now we need to tweak the data before creating the ; new child ship (in this way, the child inherits things ; like location from the parent) LDA TYPE ; Fetch the ship type of the parent into A CMP #SST ; If the parent is not a space station, jump to rx to BNE rx ; skip the following ; The parent is a space station, so the child needs to ; launch out of the space station's slot. The space ; station's nosev vector points out of the station's ; slot, so we want to move the ship along this vector. ; We do this by taking the unit vector in nosev and ; doubling it, so we spawn our ship 2 units along the ; vector from the space station's centre TXA ; Store the child's ship type in X on the stack PHA LDA #32 ; Set the child's byte #27 (speed) to 32 STA INWK+27 LDX #0 ; Add 2 * nosev_x_hi to (x_lo, x_hi, x_sign) to get the LDA INWK+10 ; child's x-coordinate JSR SFS2 LDX #3 ; Add 2 * nosev_y_hi to (y_lo, y_hi, y_sign) to get the LDA INWK+12 ; child's y-coordinate JSR SFS2 LDX #6 ; Add 2 * nosev_z_hi to (z_lo, z_hi, z_sign) to get the LDA INWK+14 ; child's z-coordinate JSR SFS2 PLA ; Restore the child's ship type from the stack into X TAX .rx LDA T1 ; Restore the child ship's AI flag from T1 and store it STA INWK+32 ; in the child's byte #32 (AI) LSR INWK+29 ; Clear bit 0 of the child's byte #29 (roll counter) so ASL INWK+29 ; that its roll dampens (so if we are spawning from a ; space station, for example, the spawned ship won't ; keep rolling forever) TXA ; Copy the child's ship type from X into A CMP #SPL+1 ; If the type of the child we are spawning is less than BCS NOIL ; #PLT or greater than #SPL - i.e. not an alloy plate, CMP #PLT ; cargo canister, boulder, asteroid or splinter - then BCC NOIL ; jump to NOIL to skip us setting up some pitch and roll ; for it PHA ; Store the child's ship type on the stack so we can ; retrieve it below JSR DORND ; Set A and X to random numbers ASL A ; Set the child's byte #30 (pitch counter) to a random STA INWK+30 ; value, and at the same time set the C flag randomly TXA ; Set the child's byte #27 (speed) to a random value AND #%00001111 ; between 0 and 15 STA INWK+27 LDA #$FF ; Set the child's byte #29 (roll counter) to a full ROR A ; roll with no damping (as bits 0 to 6 are set), so the STA INWK+29 ; canister tumbles through space, with the direction in ; bit 7 set randomly, depending on the C flag from above PLA ; Retrieve the child's ship type from the stack .NOIL JSR NWSHP ; Add a new ship of type A to the local bubble ; We have now created our child ship, so we need to ; restore all the variables we saved at the start of ; the routine, so they are preserved when we return ; from the subroutine PLA ; Restore INF(1 0) from the stack STA INF+1 PLA STA INF LDX #NI%-1 ; Now to restore the INWK workspace that we saved into ; XX3 above, so set a counter in X for NI% bytes .FRL3 LDA XX3,X ; Copy the Y-th byte of XX3 to the Y-th byte of INWK STA INWK,X DEX ; Decrement the loop counter BPL FRL3 ; Loop back to copy the next byte until we have done ; them all PLA ; Restore XX0(1 0) from the stack STA XX0+1 PLA STA XX0 PLA ; Retrieve the ship type to spawn from the stack into X TAX ; so it is preserved through calls to this routine RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: SFS2 ; Type: Subroutine ; Category: Moving ; Summary: Move a ship in space along one of the coordinate axes ; ; ------------------------------------------------------------------------------ ; ; Move a ship's coordinates by a certain amount in the direction of one of the ; axes, where X determines the axis. Mathematically speaking, this routine ; translates the ship along a single axis by a signed delta. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The amount of movement, i.e. the signed delta ; ; X Determines which coordinate axis of INWK to move: ; ; * X = 0 moves the ship along the x-axis ; ; * X = 3 moves the ship along the y-axis ; ; * X = 6 moves the ship along the z-axis ; ; ****************************************************************************** .SFS2 ASL A ; Set R = |A * 2|, with the C flag set to bit 7 of A STA R LDA #0 ; Set bit 7 of A to the C flag, i.e. the sign bit from ROR A ; the original argument in A JMP MVT1 ; Add the delta R with sign A to (x_lo, x_hi, x_sign) ; (or y or z, depending on the value in X) and return ; from the subroutine using a tail call ; ****************************************************************************** ; ; Name: LL164 ; Type: Subroutine ; Category: Drawing circles ; Summary: Make the hyperspace sound and draw the hyperspace tunnel ; ; ****************************************************************************** .LL164 JSR HYPNOISE ; Call HYPNOISE to make the sound of the hyperspace ; drive being engaged LDA #4 ; Set the step size for the hyperspace rings to 4, so ; there are more sections in the rings and they are ; quite round (compared to the step size of 8 used in ; the much more polygonal launch rings) JSR HFS2 ; Call HFS2 to draw the hyperspace tunnel rings RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LAUN ; Type: Subroutine ; Category: Drawing circles ; Summary: Make the launch sound and draw the launch tunnel ; ; ------------------------------------------------------------------------------ ; ; This is shown when launching from or docking with the space station. ; ; ****************************************************************************** .LAUN LDY #sfxwhosh ; Call the NOISE routine with Y = sfxwhosh to make the JSR NOISE ; sound of the ship launching from the station LDA #8 ; Set the step size for the launch tunnel rings to 8, so ; there are fewer sections in the rings and they are ; quite polygonal (compared to the step size of 4 used ; in the much rounder hyperspace rings) ; Fall through into HFS2 to draw the launch tunnel rings ; ****************************************************************************** ; ; Name: HFS2 ; Type: Subroutine ; Category: Drawing circles ; Summary: Draw the launch or hyperspace tunnel ; ; ------------------------------------------------------------------------------ ; ; The animation gets drawn like this. First, we draw a circle of radius 8 at the ; centre, and then double the radius, draw another circle, double the radius ; again and draw a circle, and we keep doing this until the radius is bigger ; than 160 (which goes beyond the edge of the screen, which is 256 pixels wide, ; equivalent to a radius of 128). We then repeat this whole process for an ; initial circle of radius 9, then radius 10, all the way up to radius 15. ; ; This has the effect of making the tunnel appear to be racing towards us as we ; hurtle out into hyperspace or through the space station's docking tunnel. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The step size of the straight lines making up the rings ; (4 for launch, 8 for hyperspace) ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; HFS1 Don't clear the screen, and draw 8 concentric rings ; with the step size in STP ; ; ****************************************************************************** .HFS2 STA STP ; Store the step size in A LDA QQ11 ; Store the current view type in QQ11 on the stack PHA LDA #0 ; Clear the top part of the screen, draw a border box, JSR TT66 ; and set the current view type in QQ11 to 0 (the space ; view) PLA ; Restore the view type from the stack STA QQ11 .HFS1 LDX #X ; Set K3 = #X (the x-coordinate of the centre of the STX K3 ; screen) LDX #Y ; Set K4 = #Y (the y-coordinate of the centre of the STX K4 ; screen) LDX #0 ; Set X = 0 STX XX4 ; Set XX4 = 0, which we will use as a counter for ; drawing eight concentric rings STX K3+1 ; Set the high bytes of K3(1 0) and K4(1 0) to 0 STX K4+1 .HFL5 JSR HFL1 ; Call HFL1 below to draw a set of rings, with each one ; twice the radius of the previous one, until they won't ; fit on-screen INC XX4 ; Increment the counter and fetch it into X LDX XX4 CPX #8 ; If we haven't drawn 8 sets of rings yet, loop back to BNE HFL5 ; HFL5 to draw the next ring RTS ; Return from the subroutine .HFL1 LDA XX4 ; Set K to the ring number in XX4 (0-7) + 8, so K has AND #7 ; a value of 8 to 15, which we will use as the starting CLC ; radius for our next set of rings ADC #8 STA K .HFL2 LDA #1 ; Set LSP = 1 to reset the ball line heap STA LSP JSR CIRCLE2 ; Call CIRCLE2 to draw a circle with the centre at ; (K3(1 0), K4(1 0)) and radius K ASL K ; Double the radius in K BCS HF8 ; If the radius had a 1 in bit 7 before the above shift, ; then doubling K will means the circle will no longer ; fit on the screen (which is width 256), so jump to ; HF8 to stop drawing circles LDA K ; If the radius in K <= 160, loop back to HFL2 to draw CMP #160 ; another one BCC HFL2 .HF8 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: STARS2 ; Type: Subroutine ; Category: Stardust ; Summary: Process the stardust for the left or right view ; Deep dive: Stardust in the side views ; ; ------------------------------------------------------------------------------ ; ; This moves the stardust sideways according to our speed and which side we are ; looking out of, and applies our current pitch and roll to each particle of ; dust, so the stardust moves correctly when we steer our ship. ; ; These are the calculations referred to in the commentary: ; ; 1. delta_x = 8 * 256 * speed / z_hi ; 2. x = x + delta_x ; ; 3. x = x + beta * y ; 4. y = y - beta * x ; ; 5. x = x - alpha * x * y ; 6. y = y + alpha * y * y + alpha ; ; For more information see the associated deep dive. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The view to process: ; ; * X = 1 for left view ; ; * X = 2 for right view ; ; ****************************************************************************** .STARS2 LDA #0 ; Set A to 0 so we can use it to capture a sign bit CPX #2 ; If X >= 2 then the C flag is set ROR A ; Roll the C flag into the sign bit of A and store in STA RAT ; RAT, so: ; ; * Left view, C is clear so RAT = 0 (positive) ; ; * Right view, C is set so RAT = 128 (negative) ; ; RAT represents the end of the x-axis where we want new ; stardust particles to come from: positive for the left ; view where new particles come in from the right, ; negative for the right view where new particles come ; in from the left EOR #%10000000 ; Set RAT2 to the opposite sign, so: STA RAT2 ; ; * Left view, RAT2 = 128 (negative) ; ; * Right view, RAT2 = 0 (positive) ; ; RAT2 represents the direction in which stardust ; particles should move along the x-axis: negative for ; the left view where particles go from right to left, ; positive for the right view where particles go from ; left to right JSR ST2 ; Call ST2 to flip the signs of the following if this is ; the right view: ALPHA, ALP2, ALP2+1, BET2 and BET2+1 LDY NOSTM ; Set Y to the current number of stardust particles, so ; we can use it as a counter through all the stardust .STL2 LDA SZ,Y ; Set A = ZZ = z_hi STA ZZ ; We also set ZZ to the original value of z_hi, which we ; use below to remove the existing particle LSR A ; Set A = z_hi / 8 LSR A LSR A JSR DV41 ; Call DV41 to set the following: ; ; (P R) = 256 * DELTA / A ; = 256 * speed / (z_hi / 8) ; = 8 * 256 * speed / z_hi ; ; This represents the distance we should move this ; particle along the x-axis, let's call it delta_x LDA P ; Store the high byte of delta_x in newzp STA newzp EOR RAT2 ; Set S = P but with the sign from RAT2, so we now have STA S ; the distance delta_x with the correct sign in (S R): ; ; (S R) = delta_x ; = 8 * 256 * speed / z_hi ; ; So (S R) is the delta, signed to match the direction ; the stardust should move in, which is result 1 above LDA SXL,Y ; Set (A P) = (x_hi x_lo) STA P ; = x LDA SX,Y STA X1 ; Set X1 = A, so X1 contains the original value of x_hi, ; which we use below to remove the existing particle JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = x + delta_x STA S ; Set (S R) = (A X) STX R ; = x + delta_x LDA SY,Y ; Set A = y_hi STA Y1 ; Set Y1 = A, so Y1 contains the original value of y_hi, ; which we use below to remove the existing particle EOR BET2 ; Give A the correct sign of A * beta, i.e. y_hi * beta LDX BET1 ; Fetch |beta| from BET1, the pitch angle JSR MULTS-2 ; Call MULTS-2 to calculate: ; ; (A P) = X * A ; = beta * y_hi JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = beta * y + x + delta_x STX XX ; Set XX(1 0) = (A X), which gives us results 2 and 3 STA XX+1 ; above, done at the same time: ; ; x = x + delta_x + beta * y LDX SYL,Y ; Set (S R) = (y_hi y_lo) STX R ; = y LDX Y1 STX S LDX BET1 ; Fetch |beta| from BET1, the pitch angle EOR BET2+1 ; Give A the opposite sign to x * beta JSR MULTS-2 ; Call MULTS-2 to calculate: ; ; (A P) = X * A ; = -beta * x JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = -beta * x + y STX YY ; Set YY(1 0) = (A X), which gives us result 4 above: STA YY+1 ; ; y = y - beta * x LDX ALP1 ; Set X = |alpha| from ALP2, the roll angle EOR ALP2 ; Give A the correct sign of A * alpha, i.e. y_hi * ; alpha JSR MULTS-2 ; Call MULTS-2 to calculate: ; ; (A P) = X * A ; = alpha * y STA Q ; Set Q = high byte of alpha * y LDA XX ; Set (S R) = XX(1 0) STA R ; = x LDA XX+1 ; STA S ; and set A = y_hi at the same time EOR #%10000000 ; Flip the sign of A = -x_hi JSR MAD ; Call MAD to calculate: ; ; (A X) = Q * A + (S R) ; = alpha * y * -x + x STA XX+1 ; Store the high byte A in XX+1 TXA ; Store the low byte X in x_lo STA SXL,Y ; So (XX+1 x_lo) now contains result 5 above: ; ; x = x - alpha * x * y LDA YY ; Set (S R) = YY(1 0) STA R ; = y LDA YY+1 ; STA S ; and set A = y_hi at the same time JSR MAD ; Call MAD to calculate: ; ; (A X) = Q * A + (S R) ; = alpha * y * y_hi + y STA S ; Set (S R) = (A X) STX R ; = y + alpha * y * y LDA #0 ; Set P = 0 STA P LDA ALPHA ; Set A = alpha, so: ; ; (A P) = (alpha 0) ; = alpha / 256 JSR PIX1 ; Call PIX1 to calculate the following: ; ; (YY+1 y_lo) = (A P) + (S R) ; = alpha * 256 + y + alpha * y * y ; ; i.e. y = y + alpha / 256 + alpha * y^2, which is ; result 6 above ; ; PIX1 also draws a particle at (X1, Y1) with distance ; ZZ, which will remove the old stardust particle, as we ; set X1, Y1 and ZZ to the original values for this ; particle during the calculations above ; We now have our newly moved stardust particle at ; x-coordinate (XX+1 x_lo) and y-coordinate (YY+1 y_lo) ; and distance z_hi, so we draw it if it's still on ; screen, otherwise we recycle it as a new bit of ; stardust and draw that LDA XX+1 ; Set X1 and x_hi to the high byte of XX in XX+1, so STA SX,Y ; the new x-coordinate is in (x_hi x_lo) and the high STA X1 ; byte is in X1 AND #%01111111 ; Set A = ~|x_hi|, which is the same as -(x_hi + 1) EOR #%01111111 ; using two's complement CMP newzp ; If newzp <= -(x_hi + 1), then the particle has been BCC KILL2 ; moved off the side of the screen and has wrapped BEQ KILL2 ; round to the other side, jump to KILL2 to recycle this ; particle and rejoin at STC2 with the new particle ; ; In the original BBC Micro versions, this test simply ; checks whether |x_hi| >= 116, but this version using ; newzp doesn't hard-code the screen width, so this is ; presumably a change that was introduced to support ; the different screen sizes of the other platforms LDA YY+1 ; Set Y1 and y_hi to the high byte of YY in YY+1, so STA SY,Y ; the new x-coordinate is in (y_hi y_lo) and the high STA Y1 ; byte is in Y1 AND #%01111111 ; If |y_hi| >= 116 then jump to ST5 to recycle this CMP #116 ; particle, as it's gone off the top or bottom of the BCS ST5 ; screen, and rejoin at STC2 with the new particle .STC2 JSR PIXEL2 ; Draw a stardust particle at (X1,Y1) with distance ZZ, ; i.e. draw the newly moved particle at (x_hi, y_hi) ; with distance z_hi DEY ; Decrement the loop counter to point to the next ; stardust particle BEQ ST2 ; If we have just done the last particle, skip the next ; instruction to return from the subroutine JMP STL2 ; We have more stardust to process, so jump back up to ; STL2 for the next particle ; Fall through into ST2 to restore the signs of the ; following if this is the right view: ALPHA, ALP2, ; ALP2+1, BET2 and BET2+1 .ST2 LDA ALPHA ; If this is the right view, flip the sign of ALPHA EOR RAT STA ALPHA LDA ALP2 ; If this is the right view, flip the sign of ALP2 EOR RAT STA ALP2 EOR #%10000000 ; If this is the right view, flip the sign of ALP2+1 STA ALP2+1 LDA BET2 ; If this is the right view, flip the sign of BET2 EOR RAT STA BET2 EOR #%10000000 ; If this is the right view, flip the sign of BET2+1 STA BET2+1 RTS ; Return from the subroutine .KILL2 JSR DORND ; Set A and X to random numbers STA Y1 ; Set y_hi and Y1 to random numbers, so the particle STA SY,Y ; starts anywhere along the y-axis LDA #115 ; Make sure A is at least 115 and has the sign in RAT ORA RAT STA X1 ; Set x_hi and X1 to A, so this particle starts on the STA SX,Y ; correct edge of the screen for new particles BNE STF1 ; Jump down to STF1 to set the z-coordinate (this BNE is ; effectively a JMP as A will never be zero) .ST5 JSR DORND ; Set A and X to random numbers STA X1 ; Set x_hi and X1 to random numbers, so the particle STA SX,Y ; starts anywhere along the x-axis LDA #110 ; Make sure A is at least 110 and has the sign in AL2+1, ORA ALP2+1 ; the flipped sign of the roll angle alpha STA Y1 ; Set y_hi and Y1 to A, so the particle starts at the STA SY,Y ; top or bottom edge, depending on the current roll ; angle alpha .STF1 JSR DORND ; Set A and X to random numbers ORA #8 ; Make sure A is at least 8 and store it in z_hi and STA ZZ ; ZZ, so the new particle starts at any distance from STA SZ,Y ; us, but not too close BNE STC2 ; Jump up to STC2 to draw this new particle (this BNE is ; effectively a JMP as A will never be zero) ; ****************************************************************************** ; ; Name: MU5 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Set K(3 2 1 0) = (A A A A) and clear the C flag ; ; ------------------------------------------------------------------------------ ; ; In practice this is only called via a BEQ following an AND instruction, in ; which case A = 0, so this routine effectively does this: ; ; K(3 2 1 0) = 0 ; ; ****************************************************************************** .MU5 STA K ; Set K(3 2 1 0) to (A A A A) STA K+1 STA K+2 STA K+3 CLC ; Clear the C flag RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MULT3 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate K(3 2 1 0) = (A P+1 P) * Q ; Deep dive: Shift-and-add multiplication ; ; ------------------------------------------------------------------------------ ; ; Calculate the following multiplication between a signed 24-bit number and a ; signed 8-bit number, returning the result as a signed 32-bit number: ; ; K(3 2 1 0) = (A P+1 P) * Q ; ; The algorithm is the same shift-and-add algorithm as in routine MULT1, but ; extended to cope with more bits. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag The C flag is cleared ; ; ****************************************************************************** .MULT3 STA R ; Store the high byte of (A P+1 P) in R AND #%01111111 ; Set K+2 to |A|, the high byte of K(2 1 0) STA K+2 LDA Q ; Set A to bits 0-6 of Q, so A = |Q| AND #%01111111 BEQ MU5 ; If |Q| = 0, jump to MU5 to set K(3 2 1 0) to 0, ; returning from the subroutine using a tail call SEC ; Set T = |Q| - 1 SBC #1 STA T ; We now use the same shift-and-add algorithm as MULT1 ; to calculate the following: ; ; K(2 1 0) = K(2 1 0) * |Q| ; ; so we start with the first shift right, in which we ; take (K+2 P+1 P) and shift it right, storing the ; result in K(2 1 0), ready for the multiplication loop ; (so the multiplication loop actually calculates ; (|A| P+1 P) * |Q|, as the following sets K(2 1 0) to ; (|A| P+1 P) shifted right) LDA P+1 ; Set A = P+1 LSR K+2 ; Shift the high byte in K+2 to the right ROR A ; Shift the middle byte in A to the right and store in STA K+1 ; K+1 (so K+1 contains P+1 shifted right) LDA P ; Shift the middle byte in P to the right and store in ROR A ; K, so K(2 1 0) now contains (|A| P+1 P) shifted right STA K ; We now use the same shift-and-add algorithm as MULT1 ; to calculate the following: ; ; K(2 1 0) = K(2 1 0) * |Q| LDA #0 ; Set A = 0 so we can start building the answer in A LDX #24 ; Set up a counter in X to count the 24 bits in K(2 1 0) .MUL2 BCC P%+4 ; If C (i.e. the next bit from K) is set, do the ADC T ; addition for this bit of K: ; ; A = A + T + C ; = A + |Q| - 1 + 1 ; = A + |Q| ROR A ; Shift A right by one place to catch the next digit ROR K+2 ; next digit of our result in the left end of K(2 1 0), ROR K+1 ; while also shifting K(2 1 0) right to fetch the next ROR K ; bit for the calculation into the C flag ; ; On the last iteration of this loop, the bit falling ; off the end of K will be bit 0 of the original A, as ; we did one shift before the loop and we are doing 24 ; iterations. We set A to 0 before looping, so this ; means the loop exits with the C flag clear DEX ; Decrement the loop counter BNE MUL2 ; Loop back for the next bit until K(2 1 0) has been ; rotated all the way ; The result (|A| P+1 P) * |Q| is now in (A K+2 K+1 K), ; but it is positive and doesn't have the correct sign ; of the final result yet STA T ; Save the high byte of the result into T LDA R ; Fetch the sign byte from the original (A P+1 P) ; argument that we stored in R EOR Q ; EOR with Q so the sign bit is the same as that of ; (A P+1 P) * Q AND #%10000000 ; Extract the sign bit ORA T ; Apply this to the high byte of the result in T, so ; that A now has the correct sign for the result, and ; (A K+2 K+1 K) therefore contains the correctly signed ; result STA K+3 ; Store A in K+3, so K(3 2 1 0) now contains the result RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MLS2 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (S R) = XX(1 0) and (A P) = A * ALP1 ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; (S R) = XX(1 0) ; ; (A P) = A * ALP1 ; ; where ALP1 is the magnitude of the current roll angle alpha, in the range ; 0-31. ; ; ****************************************************************************** .MLS2 LDX XX ; Set (S R) = XX(1 0), starting with the low bytes STX R LDX XX+1 ; And then doing the high bytes STX S ; Fall through into MLS1 to calculate (A P) = A * ALP1 ; ****************************************************************************** ; ; Name: MLS1 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (A P) = ALP1 * A ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; (A P) = ALP1 * A ; ; where ALP1 is the magnitude of the current roll angle alpha, in the range ; 0-31. ; ; This routine uses an unrolled version of MU11. MU11 calculates P * X, so we ; use the same algorithm but with P set to ALP1 and X set to A. The unrolled ; version here can skip the bit tests for bits 5-7 of P as we know P < 32, so ; only 5 shifts with bit tests are needed (for bits 0-4), while the other 3 ; shifts can be done without a test (for bits 5-7). ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; MULTS-2 Calculate (A P) = X * A ; ; ****************************************************************************** .MLS1 LDX ALP1 ; Set P to the roll angle alpha magnitude in ALP1 STX P ; (0-31), so now we calculate P * A .MULTS TAX ; Set X = A, so now we can calculate P * X instead of ; P * A to get our result, and we can use the algorithm ; from MU11 to do that, just unrolled (as MU11 returns ; P * X) AND #%10000000 ; Set T to the sign bit of A STA T TXA ; Set A = |A| AND #%01111111 BEQ MU6 ; If A = 0, jump to MU6 to set P(1 0) = 0 and return ; from the subroutine using a tail call TAX ; Set T1 = X - 1 DEX ; STX T1 ; We subtract 1 as the C flag will be set when we want ; to do an addition in the loop below LDA #0 ; Set A = 0 so we can start building the answer in A LSR P ; Set P = P >> 1 ; and C flag = bit 0 of P ; We are now going to work our way through the bits of ; P, and do a shift-add for any bits that are set, ; keeping the running total in A, but instead of using a ; loop like MU11, we just unroll it, starting with bit 0 BCC P%+4 ; If C (i.e. the next bit from P) is set, do the ADC T1 ; addition for this bit of P: ; ; A = A + T1 + C ; = A + X - 1 + 1 ; = A + X ROR A ; Shift A right to catch the next digit of our result, ; which the next ROR sticks into the left end of P while ; also extracting the next bit of P ROR P ; Add the overspill from shifting A to the right onto ; the start of P, and shift P right to fetch the next ; bit for the calculation into the C flag BCC P%+4 ; Repeat the shift-and-add loop for bit 1 ADC T1 ROR A ROR P BCC P%+4 ; Repeat the shift-and-add loop for bit 2 ADC T1 ROR A ROR P BCC P%+4 ; Repeat the shift-and-add loop for bit 3 ADC T1 ROR A ROR P BCC P%+4 ; Repeat the shift-and-add loop for bit 4 ADC T1 ROR A ROR P LSR A ; Just do the "shift" part for bit 5 ROR P LSR A ; Just do the "shift" part for bit 6 ROR P LSR A ; Just do the "shift" part for bit 7 ROR P ORA T ; Give A the sign bit of the original argument A that ; we put into T above RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MU6 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Set P(1 0) = (A A) ; ; ------------------------------------------------------------------------------ ; ; In practice this is only called via a BEQ following an AND instruction, in ; which case A = 0, so this routine effectively does this: ; ; P(1 0) = 0 ; ; ****************************************************************************** .MU6 STA P+1 ; Set P(1 0) = (A A) STA P RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: SQUA ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Clear bit 7 of A and calculate (A P) = A * A ; ; ------------------------------------------------------------------------------ ; ; Do the following multiplication of unsigned 8-bit numbers, after first ; clearing bit 7 of A: ; ; (A P) = A * A ; ; ****************************************************************************** .SQUA AND #%01111111 ; Clear bit 7 of A and fall through into SQUA2 to set ; (A P) = A * A ; ****************************************************************************** ; ; Name: SQUA2 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (A P) = A * A ; ; ------------------------------------------------------------------------------ ; ; Do the following multiplication of unsigned 8-bit numbers: ; ; (A P) = A * A ; ; ****************************************************************************** .SQUA2 STA P ; Copy A into P and X TAX BNE MU11 ; If X = 0 fall through into MU1 to return a 0, ; otherwise jump to MU11 to return P * X ; ****************************************************************************** ; ; Name: MU1 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Copy X into P and A, and clear the C flag ; ; ------------------------------------------------------------------------------ ; ; Used to return a 0 result quickly from MULTU below. ; ; ****************************************************************************** .MU1 CLC ; Clear the C flag STX P ; Copy X into P and A TXA RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MLU1 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate Y1 = y_hi and (A P) = |y_hi| * Q for Y-th stardust ; ; ------------------------------------------------------------------------------ ; ; Do the following assignment, and multiply the Y-th stardust particle's ; y-coordinate with an unsigned number Q: ; ; Y1 = y_hi ; ; (A P) = |y_hi| * Q ; ; ****************************************************************************** .MLU1 LDA SY,Y ; Set Y1 the Y-th byte of SY STA Y1 ; Fall through into MLU2 to calculate: ; ; (A P) = |A| * Q ; ****************************************************************************** ; ; Name: MLU2 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (A P) = |A| * Q ; ; ------------------------------------------------------------------------------ ; ; Do the following multiplication of a sign-magnitude 8-bit number P with an ; unsigned number Q: ; ; (A P) = |A| * Q ; ; ****************************************************************************** .MLU2 AND #%01111111 ; Clear the sign bit in P, so P = |A| STA P ; Fall through into MULTU to calculate: ; ; (A P) = P * Q ; = |A| * Q ; ****************************************************************************** ; ; Name: MULTU ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (A P) = P * Q ; ; ------------------------------------------------------------------------------ ; ; Do the following multiplication of unsigned 8-bit numbers: ; ; (A P) = P * Q ; ; ****************************************************************************** .MULTU LDX Q ; Set X = Q BEQ MU1 ; If X = Q = 0, jump to MU1 to copy X into P and A, ; clear the C flag and return from the subroutine using ; a tail call ; Otherwise fall through into MU11 to set (A P) = P * X ; ****************************************************************************** ; ; Name: MU11 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (A P) = P * X ; Deep dive: Shift-and-add multiplication ; ; ------------------------------------------------------------------------------ ; ; Do the following multiplication of two unsigned 8-bit numbers: ; ; (A P) = P * X ; ; This uses the same shift-and-add approach as MULT1, but it's simpler as we ; are dealing with unsigned numbers in P and X. ; ; ****************************************************************************** .MU11 DEX ; Set T = X - 1 STX T ; ; We subtract 1 as the C flag will be set when we want ; to do an addition in the loop below LDA #0 ; Set A = 0 so we can start building the answer in A ;LDX #8 ; This instruction is commented out in the original ; source TAX ; Copy A into X. There is a comment in the original ; source here that says "just in case", which refers to ; the MU11 routine in the BBC Micro cassette and disc ; versions, which set X to 0 (as they use X as a loop ; counter) ; ; The version here doesn't use a loop, but this ; instruction makes sure the unrolled version returns ; the same results as the loop versions, just in case ; something out there relies on MU11 returning X = 0 LSR P ; Set P = P >> 1 ; and C flag = bit 0 of P ; We now repeat the following four instruction block ; eight times, one for each bit in P. In the BBC Micro ; cassette and disc versions of Elite the following is ; done with a loop, but it is marginally faster to ; unroll the loop and have eight copies of the code, ; though it does take up a bit more memory (though that ; isn't a big concern when you have a 6502 Second ; Processor) BCC P%+4 ; If C (i.e. bit 0 of P) is set, do the ADC T ; addition for this bit of P: ; ; A = A + T + C ; = A + X - 1 + 1 ; = A + X ROR A ; Shift A right to catch the next digit of our result, ; which the next ROR sticks into the left end of P while ; also extracting the next bit of P ROR P ; Add the overspill from shifting A to the right onto ; the start of P, and shift P right to fetch the next ; bit for the calculation into the C flag BCC P%+4 ; Repeat for the second time ADC T ROR A ROR P BCC P%+4 ; Repeat for the third time ADC T ROR A ROR P BCC P%+4 ; Repeat for the fourth time ADC T ROR A ROR P BCC P%+4 ; Repeat for the fifth time ADC T ROR A ROR P BCC P%+4 ; Repeat for the sixth time ADC T ROR A ROR P BCC P%+4 ; Repeat for the seventh time ADC T ROR A ROR P BCC P%+4 ; Repeat for the eighth time ADC T ROR A ROR P RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: FMLTU2 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate A = K * sin(A) ; Deep dive: The sine, cosine and arctan tables ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; A = K * sin(A) ; ; Because this routine uses the sine lookup table SNE, we can also call this ; routine to calculate cosine multiplication. To calculate the following: ; ; A = K * cos(B) ; ; call this routine with B + 16 in the accumulator, as sin(B + 16) = cos(B). ; ; ****************************************************************************** .FMLTU2 AND #%00011111 ; Restrict A to bits 0-5 (so it's in the range 0-31) TAX ; Set Q = sin(A) * 256 LDA SNE,X STA Q LDA K ; Set A to the radius in K ; Fall through into FMLTU to do the following: ; ; (A ?) = A * Q ; = K * sin(A) * 256 ; ; which is equivalent to: ; ; A = K * sin(A) ; ****************************************************************************** ; ; Name: FMLTU ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate A = A * Q / 256 ; Deep dive: Multiplication and division using logarithms ; ; ------------------------------------------------------------------------------ ; ; Do the following multiplication of two unsigned 8-bit numbers, returning only ; the high byte of the result: ; ; (A ?) = A * Q ; ; or, to put it another way: ; ; A = A * Q / 256 ; ; The advanced versions of Elite use logarithms to speed up the multiplication ; process. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag The C flag is clear if A = 0, or set if we return a ; result from one of the log tables ; ; ****************************************************************************** .FMLTU STX P ; Store X in P so we can preserve it through the call to ; FMLTU STA widget ; Store A in widget, so now widget = argument A TAX ; Transfer A into X, so now X = argument A BEQ MU3 ; If A = 0, jump to MU3 to return a result of 0, as ; 0 * Q / 256 is always 0 ; We now want to calculate La + Lq, first adding the low ; bytes (from the logL table), and then the high bytes ; (from the log table) LDA logL,X ; Set A = low byte of La ; = low byte of La (as we set X to A above) LDX Q ; Set X = Q BEQ MU3again ; If X = 0, jump to MU3again to return a result of 0, as ; A * 0 / 256 is always 0 CLC ; Set A = A + low byte of Lq ADC logL,X ; = low byte of La + low byte of Lq BMI oddlog ; If A > 127, jump to oddlog LDA log,X ; Set A = high byte of Lq LDX widget ; Set A = A + C + high byte of La ADC log,X ; = high byte of Lq + high byte of La + C ; ; so we now have: ; ; A = high byte of (La + Lq) BCC MU3again ; If the addition fitted into one byte and didn't carry, ; then La + Lq < 256, so we jump to MU3again to return a ; result of 0 and the C flag clear ; If we get here then the C flag is set, ready for when ; we return from the subroutine below TAX ; Otherwise La + Lq >= 256, so we return the A-th entry LDA antilog,X ; from the antilog table LDX P ; Restore X from P so it is preserved RTS ; Return from the subroutine .oddlog LDA log,X ; Set A = high byte of Lq LDX widget ; Set A = A + C + high byte of La ADC log,X ; = high byte of Lq + high byte of La + C ; ; so we now have: ; ; A = high byte of (La + Lq) BCC MU3again ; If the addition fitted into one byte and didn't carry, ; then La + Lq < 256, so we jump to MU3again to return a ; result of 0 and the C flag clear ; If we get here then the C flag is set, ready for when ; we return from the subroutine below TAX ; Otherwise La + Lq >= 256, so we return the A-th entry LDA antilogODD,X ; from the antilogODD table .MU3 ; If we get here then A (our result) is already 0 LDX P ; Restore X from P so it is preserved RTS ; Return from the subroutine .MU3again LDA #0 ; Set A = 0 LDX P ; Restore X from P so it is preserved RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MLTU2 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (A P+1 P) = (A ~P) * Q ; Deep dive: Shift-and-add multiplication ; ; ------------------------------------------------------------------------------ ; ; Do the following multiplication of an unsigned 16-bit number and an unsigned ; 8-bit number: ; ; (A P+1 P) = (A ~P) * Q ; ; where ~P means P EOR %11111111 (i.e. P with all its bits flipped). In other ; words, if you wanted to calculate $1234 * $56, you would: ; ; * Set A to $12 ; * Set P to $34 EOR %11111111 = $CB ; * Set Q to $56 ; ; before calling MLTU2. ; ; This routine is like a mash-up of MU11 and FMLTU. It uses part of FMLTU's ; inverted argument trick to work out whether or not to do an addition, and like ; MU11 it sets up a counter in X to extract bits from (P+1 P). But this time we ; extract 16 bits from (P+1 P), so the result is a 24-bit number. The core of ; the algorithm is still the shift-and-add approach explained in MULT1, just ; with more bits. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Q Q is preserved ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; MLTU2-2 Set Q to X, so this calculates (A P+1 P) = (A ~P) * X ; ; ****************************************************************************** STX Q ; Store X in Q .MLTU2 EOR #%11111111 ; Flip the bits in A and rotate right, storing the LSR A ; result in P+1, so we now calculate (P+1 P) * Q STA P+1 LDA #0 ; Set A = 0 so we can start building the answer in A LDX #16 ; Set up a counter in X to count the 16 bits in (P+1 P) ROR P ; Set P = P >> 1 with bit 7 = bit 0 of A ; and C flag = bit 0 of P .MUL7 BCS MU21 ; If C (i.e. the next bit from P) is set, do not do the ; addition for this bit of P, and instead skip to MU21 ; to just do the shifts ADC Q ; Do the addition for this bit of P: ; ; A = A + Q + C ; = A + Q ROR A ; Rotate (A P+1 P) to the right, so we capture the next ROR P+1 ; digit of the result in P+1, and extract the next digit ROR P ; of (P+1 P) in the C flag DEX ; Decrement the loop counter BNE MUL7 ; Loop back for the next bit until P has been rotated ; all the way RTS ; Return from the subroutine .MU21 LSR A ; Shift (A P+1 P) to the right, so we capture the next ROR P+1 ; digit of the result in P+1, and extract the next digit ROR P ; of (P+1 P) in the C flag DEX ; Decrement the loop counter BNE MUL7 ; Loop back for the next bit until P has been rotated ; all the way RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MUT3 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: An unused routine that does the same as MUT2 ; ; ------------------------------------------------------------------------------ ; ; This routine is never actually called, but it is identical to MUT2, as the ; extra instructions have no effect. ; ; ****************************************************************************** .MUT3 LDX ALP1 ; Set P = ALP1, though this gets overwritten by the STX P ; following, so this has no effect ; Fall through into MUT2 to do the following: ; ; (S R) = XX(1 0) ; (A P) = Q * A ; ****************************************************************************** ; ; Name: MUT2 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (S R) = XX(1 0) and (A P) = Q * A ; ; ------------------------------------------------------------------------------ ; ; Do the following assignment, and multiplication of two signed 8-bit numbers: ; ; (S R) = XX(1 0) ; (A P) = Q * A ; ; ****************************************************************************** .MUT2 LDX XX+1 ; Set S = XX+1 STX S ; Fall through into MUT1 to do the following: ; ; R = XX ; (A P) = Q * A ; ****************************************************************************** ; ; Name: MUT1 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate R = XX and (A P) = Q * A ; ; ------------------------------------------------------------------------------ ; ; Do the following assignment, and multiplication of two signed 8-bit numbers: ; ; R = XX ; (A P) = Q * A ; ; ****************************************************************************** .MUT1 LDX XX ; Set R = XX STX R ; Fall through into MULT1 to do the following: ; ; (A P) = Q * A ; ****************************************************************************** ; ; Name: MULT1 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (A P) = Q * A ; Deep dive: Shift-and-add multiplication ; ; ------------------------------------------------------------------------------ ; ; Do the following multiplication of two 8-bit sign-magnitude numbers: ; ; (A P) = Q * A ; ; ****************************************************************************** .MULT1 TAX ; Store A in X AND #%01111111 ; Set P = |A| >> 1 LSR A ; and C flag = bit 0 of A STA P TXA ; Restore argument A EOR Q ; Set bit 7 of A and T if Q and A have different signs, AND #%10000000 ; clear bit 7 if they have the same signs, 0 all other STA T ; bits, i.e. T contains the sign bit of Q * A LDA Q ; Set A = |Q| AND #%01111111 BEQ mu10 ; If |Q| = 0 jump to mu10 (with A set to 0) TAX ; Set T1 = |Q| - 1 DEX ; STX T1 ; We subtract 1 as the C flag will be set when we want ; to do an addition in the loop below ; We are now going to work our way through the bits of ; P, and do a shift-add for any bits that are set, ; keeping the running total in A. We already set up ; the first shift at the start of this routine, as ; P = |A| >> 1 and C = bit 0 of A, so we now need to set ; up a loop to sift through the other 7 bits in P LDA #0 ; Set A = 0 so we can start building the answer in A TAX ; Copy A into X. There is a comment in the original ; source here that says "just in case", which refers to ; the MULT1 routine in the BBC Micro cassette and disc ; versions, which set X to 0 (as they use X as a loop ; counter) ; ; The version here doesn't use a loop, but this ; instruction makes sure the unrolled version returns ; the same results as the loop versions, just in case ; something out there relies on MULT1 returning X = 0 ;.MUL4 ; These instructions are commented out in the original ; ; source. They contain the original loop version of the ;BCC P%+4 ; code that's used in the BBC Micro cassette and disc ;ADC T1 ; versions ; ;ROR A ;ROR P ; ;DEX ; ;BNE MUL4 ; ;LSR A ;ROR P ; ;ORA T ; ;RTS ; ;.mu10 ; ;STA P ; ;RTS ; We now repeat the following four instruction block ; seven times, one for each remaining bit in P. In the ; BBC Micro cassette and disc versions of Elite the ; following is done with a loop, but it is marginally ; faster to unroll the loop and have seven copies of ; the code, though it does take up a bit more memory BCC P%+4 ; If C (i.e. the next bit from P) is set, do the ADC T1 ; addition for this bit of P: ; ; A = A + T1 + C ; = A + |Q| - 1 + 1 ; = A + |Q| ROR A ; As mentioned above, this ROR shifts A right and ; catches bit 0 in C - giving another digit for our ; result - and the next ROR sticks that bit into the ; left end of P while also extracting the next bit of P ; for the next addition ROR P ; Add the overspill from shifting A to the right onto ; the start of P, and shift P right to fetch the next ; bit for the calculation BCC P%+4 ; Repeat for the second time ADC T1 ROR A ROR P BCC P%+4 ; Repeat for the third time ADC T1 ROR A ROR P BCC P%+4 ; Repeat for the fourth time ADC T1 ROR A ROR P BCC P%+4 ; Repeat for the fifth time ADC T1 ROR A ROR P BCC P%+4 ; Repeat for the sixth time ADC T1 ROR A ROR P BCC P%+4 ; Repeat for the seventh time ADC T1 ROR A ROR P LSR A ; Rotate (A P) once more to get the final result, as ROR P ; we only pushed 7 bits through the above process ORA T ; Set the sign bit of the result that we stored in T RTS ; Return from the subroutine .mu10 STA P ; If we get here, the result is 0 and A = 0, so set ; P = 0 so (A P) = 0 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MULT12 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (S R) = Q * A ; ; ------------------------------------------------------------------------------ ; ; Calculate: ; ; (S R) = Q * A ; ; ****************************************************************************** .MULT12 JSR MULT1 ; Set (A P) = Q * A STA S ; Set (S R) = (A P) LDA P ; = Q * A STA R RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TAS3 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Calculate the dot product of XX15 and an orientation vector ; ; ------------------------------------------------------------------------------ ; ; Calculate the dot product of the vector in XX15 and one of the orientation ; vectors, as determined by the value of Y. If vect is the orientation vector, ; we calculate this: ; ; (A X) = vect . XX15 ; = vect_x * XX15 + vect_y * XX15+1 + vect_z * XX15+2 ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The orientation vector: ; ; * If Y = 10, calculate nosev . XX15 ; ; * If Y = 16, calculate roofv . XX15 ; ; * If Y = 22, calculate sidev . XX15 ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; (A X) The result of the dot product ; ; ****************************************************************************** .TAS3 LDX INWK,Y ; Set Q = the Y-th byte of INWK, i.e. vect_x STX Q LDA XX15 ; Set A = XX15 JSR MULT12 ; Set (S R) = Q * A ; = vect_x * XX15 LDX INWK+2,Y ; Set Q = the Y+2-th byte of INWK, i.e. vect_y STX Q LDA XX15+1 ; Set A = XX15+1 JSR MAD ; Set (A X) = Q * A + (S R) ; = vect_y * XX15+1 + vect_x * XX15 STA S ; Set (S R) = (A X) STX R LDX INWK+4,Y ; Set Q = the Y+2-th byte of INWK, i.e. vect_z STX Q LDA XX15+2 ; Set A = XX15+2 ; Fall through into MAD to set: ; ; (A X) = Q * A + (S R) ; = vect_z * XX15+2 + vect_y * XX15+1 + ; vect_x * XX15 ; ****************************************************************************** ; ; Name: MAD ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (A X) = Q * A + (S R) ; ; ------------------------------------------------------------------------------ ; ; Calculate ; ; (A X) = Q * A + (S R) ; ; ****************************************************************************** .MAD JSR MULT1 ; Call MULT1 to set (A P) = Q * A ; Fall through into ADD to do: ; ; (A X) = (A P) + (S R) ; = Q * A + (S R) ; ****************************************************************************** ; ; Name: ADD ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (A X) = (A P) + (S R) ; Deep dive: Adding sign-magnitude numbers ; ; ------------------------------------------------------------------------------ ; ; Add two 16-bit sign-magnitude numbers together, calculating: ; ; (A X) = (A P) + (S R) ; ; ****************************************************************************** .ADD STA T1 ; Store argument A in T1 AND #%10000000 ; Extract the sign (bit 7) of A and store it in T STA T EOR S ; EOR bit 7 of A with S. If they have different bit 7s BMI MU8 ; (i.e. they have different signs) then bit 7 in the ; EOR result will be 1, which means the EOR result is ; negative. So the AND, EOR and BMI together mean "jump ; to MU8 if A and S have different signs" ; If we reach here, then A and S have the same sign, so ; we can add them and set the sign to get the result LDA R ; Add the least significant bytes together into X: CLC ; ADC P ; X = P + R TAX LDA S ; Add the most significant bytes together into A. We ADC T1 ; stored the original argument A in T1 earlier, so we ; can do this with: ; ; A = A + S + C ; = T1 + S + C ORA T ; If argument A was negative (and therefore S was also ; negative) then make sure result A is negative by ; OR'ing the result with the sign bit from argument A ; (which we stored in T) RTS ; Return from the subroutine .MU8 ; If we reach here, then A and S have different signs, ; so we can subtract their absolute values and set the ; sign to get the result LDA S ; Clear the sign (bit 7) in S and store the result in AND #%01111111 ; U, so U now contains |S| STA U LDA P ; Subtract the least significant bytes into X: SEC ; SBC R ; X = P - R TAX LDA T1 ; Restore the A of the argument (A P) from T1 and AND #%01111111 ; clear the sign (bit 7), so A now contains |A| SBC U ; Set A = |A| - |S| ; At this point we have |A P| - |S R| in (A X), so we ; need to check whether the subtraction above was the ; right way round (i.e. that we subtracted the smaller ; absolute value from the larger absolute value) BCS MU9 ; If |A| >= |S|, our subtraction was the right way ; round, so jump to MU9 to set the sign ; If we get here, then |A| < |S|, so our subtraction ; above was the wrong way round (we actually subtracted ; the larger absolute value from the smaller absolute ; value). So let's subtract the result we have in (A X) ; from zero, so that the subtraction is the right way ; round STA U ; Store A in U TXA ; Set X = 0 - X using two's complement (to negate a EOR #$FF ; number in two's complement, you can invert the bits ADC #1 ; and add one - and we know the C flag is clear as we TAX ; didn't take the BCS branch above, so the ADC will do ; the correct addition) LDA #0 ; Set A = 0 - A, which we can do this time using a SBC U ; subtraction with the C flag clear ORA #%10000000 ; We now set the sign bit of A, so that the EOR on the ; next line will give the result the opposite sign to ; argument A (as T contains the sign bit of argument ; A). This is the same as giving the result the same ; sign as argument S (as A and S have different signs), ; which is what we want, as S has the larger absolute ; value .MU9 EOR T ; If we get here from the BCS above, then |A| >= |S|, ; so we want to give the result the same sign as ; argument A, so if argument A was negative, we flip ; the sign of the result with an EOR (to make it ; negative) RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TIS1 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (A ?) = (-X * A + (S R)) / 96 ; Deep dive: Shift-and-subtract division ; ; ------------------------------------------------------------------------------ ; ; Calculate the following expression between sign-magnitude numbers, ignoring ; the low byte of the result: ; ; (A ?) = (-X * A + (S R)) / 96 ; ; This uses the same shift-and-subtract algorithm as TIS2, just with the ; quotient A hard-coded to 96. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Q Gets set to the value of argument X ; ; ****************************************************************************** .TIS1 STX Q ; Set Q = X EOR #%10000000 ; Flip the sign bit in A JSR MAD ; Set (A X) = Q * A + (S R) ; = X * -A + (S R) .DVID96 TAX ; Set T to the sign bit of the result AND #%10000000 STA T TXA ; Set A to the high byte of the result with the sign bit AND #%01111111 ; cleared, so (A ?) = |X * A + (S R)| ; The following is identical to TIS2, except Q is ; hard-coded to 96, so this does A = A / 96 LDX #254 ; Set T1 to have bits 1-7 set, so we can rotate through STX T1 ; 7 loop iterations, getting a 1 each time, and then ; getting a 0 on the 8th iteration... and we can also ; use T1 to catch our result bits into bit 0 each time .DVL3 ASL A ; Shift A to the left CMP #96 ; If A < 96 skip the following subtraction BCC DV4 SBC #96 ; Set A = A - 96 ; ; Going into this subtraction we know the C flag is ; set as we passed through the BCC above, and we also ; know that A >= 96, so the C flag will still be set ; once we are done .DV4 ROL T1 ; Rotate the counter in T1 to the left, and catch the ; result bit into bit 0 (which will be a 0 if we didn't ; do the subtraction, or 1 if we did) BCS DVL3 ; If we still have set bits in T1, loop back to DVL3 to ; do the next iteration of 7 LDA T1 ; Fetch the result from T1 into A ORA T ; Give A the sign of the result that we stored above RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DV42 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (P R) = 256 * DELTA / z_hi ; ; ------------------------------------------------------------------------------ ; ; Calculate the following division and remainder: ; ; P = DELTA / (the Y-th stardust particle's z_hi coordinate) ; ; R = remainder as a fraction of A, where 1.0 = 255 ; ; Another way of saying the above is this: ; ; (P R) = 256 * DELTA / z_hi ; ; DELTA is a value between 1 and 40, and the minimum z_hi is 16 (dust particles ; are removed at lower values than this), so this means P is between 0 and 2 ; (as 40 / 16 = 2.5, so the maximum result is P = 2 and R = 128. ; ; This uses the same shift-and-subtract algorithm as TIS2, but this time we ; keep the remainder. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The number of the stardust particle to process ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag The C flag is cleared ; ; ****************************************************************************** .DV42 LDA SZ,Y ; Fetch the Y-th dust particle's z_hi coordinate into A ; Fall through into DV41 to do: ; ; (P R) = 256 * DELTA / A ; = 256 * DELTA / Y-th stardust particle's z_hi ; ****************************************************************************** ; ; Name: DV41 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (P R) = 256 * DELTA / A ; ; ------------------------------------------------------------------------------ ; ; Calculate the following division and remainder: ; ; P = DELTA / A ; ; R = remainder as a fraction of A, where 1.0 = 255 ; ; Another way of saying the above is this: ; ; (P R) = 256 * DELTA / A ; ; This uses the same shift-and-subtract algorithm as TIS2, but this time we ; keep the remainder. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag The C flag is cleared ; ; ****************************************************************************** .DV41 STA Q ; Store A in Q LDA DELTA ; Fetch the speed from DELTA into A ; Fall through into DVID4 to do: ; ; (P R) = 256 * A / Q ; = 256 * DELTA / A ; ****************************************************************************** ; ; Name: DVID4 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (P R) = 256 * A / Q ; Deep dive: Shift-and-subtract division ; ; ------------------------------------------------------------------------------ ; ; Calculate the following division and remainder: ; ; P = A / Q ; ; R = remainder as a fraction of Q, where 1.0 = 255 ; ; Another way of saying the above is this: ; ; (P R) = 256 * A / Q ; ; This uses the same shift-and-subtract algorithm as TIS2, but this time we ; keep the remainder and the loop is unrolled. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag The C flag is cleared ; ; ****************************************************************************** .DVID4 ;LDX #8 ; This instruction is commented out in the original ; source ASL A ; Shift A left and store in P (we will build the result STA P ; in P) LDA #0 ; Set A = 0 for us to build a remainder ;.DVL4 ; This label is commented out in the original source ; We now repeat the following five instruction block ; eight times, one for each bit in P. In the BBC Micro ; cassette and disc versions of Elite the following is ; done with a loop, but it is marginally faster to ; unroll the loop and have eight copies of the code, ; though it does take up a bit more memory (though that ; isn't a big concern when you have a 6502 Second ; Processor) ROL A ; Shift A to the left CMP Q ; If A < Q skip the following subtraction BCC P%+4 SBC Q ; A >= Q, so set A = A - Q ROL P ; Shift P to the left, pulling the C flag into bit 0 ROL A ; Repeat for the second time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the third time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the fourth time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the fifth time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the sixth time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the seventh time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the eighth time CMP Q BCC P%+4 SBC Q ROL P LDX #0 ; Set X = 0 so this unrolled version of DVID4 also ; returns X = 0 STA widget ; This contains the code from the LL28+4 routine, so TAX ; this section is exactly equivalent to a JMP LL28+4 BEQ LLfix22 ; call, but is slightly faster as it's been inlined LDA logL,X ; (so it converts the remainder in A into an integer LDX Q ; representation of the fractional value A / Q, in R, SEC ; where 1.0 = 255, and it also clears the C flag SBC logL,X BMI noddlog22 LDX widget LDA log,X LDX Q SBC log,X BCS LL222 TAX LDA antilog,X .LLfix22 STA R ; This is also part of the inline LL28+4 routine RTS .LL222 LDA #255 ; This is also part of the inline LL28+4 routine STA R RTS .noddlog22 LDX widget ; This is also part of the inline LL28+4 routine LDA log,X LDX Q SBC log,X BCS LL222 TAX LDA antilogODD,X STA R RTS ; ****************************************************************************** ; ; Name: DVID3B2 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_lo) ; Deep dive: Shift-and-subtract division ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_lo) ; ; The actual division here is done as an 8-bit calculation using LL31, but this ; routine shifts both the numerator (the top part of the division) and the ; denominator (the bottom part of the division) around to get the multi-byte ; result we want. ; ; Specifically, it shifts both of them to the left as far as possible, keeping a ; tally of how many shifts get done in each one - and specifically, the ; difference in the number of shifts between the top and bottom (as shifting ; both of them once in the same direction won't change the result). It then ; divides the two highest bytes with the simple 8-bit routine in LL31, and ; shifts the result by the difference in the number of shifts, which acts as a ; scale factor to get the correct result. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; K(3 2 1 0) The result of the division ; ; X X is preserved ; ; ****************************************************************************** .DVID3B2 STA P+2 ; Set P+2 = A LDA INWK+6 ; Set Q = z_lo, making sure Q is at least 1 ORA #1 STA Q LDA INWK+7 ; Set R = z_hi STA R LDA INWK+8 ; Set S = z_sign STA S .DVID3B ; Given the above assignments, we now want to calculate ; the following to get the result we want: ; ; K(3 2 1 0) = P(2 1 0) / (S R Q) LDA P ; Make sure P(2 1 0) is at least 1 ORA #1 STA P LDA P+2 ; Set T to the sign of P+2 * S (i.e. the sign of the EOR S ; result) and store it in T AND #%10000000 STA T LDY #0 ; Set Y = 0 to store the scale factor LDA P+2 ; Clear the sign bit of P+2, so the division can be done AND #%01111111 ; with positive numbers and we'll set the correct sign ; below, once all the maths is done ; ; This also leaves A = P+2, which we use below .DVL9 ; We now shift (A P+1 P) left until A >= 64, counting ; the number of shifts in Y. This makes the top part of ; the division as large as possible, thus retaining as ; much accuracy as we can. When we come to return the ; final result, we shift the result by the number of ; places in Y, and in the correct direction CMP #64 ; If A >= 64, jump down to DV14 BCS DV14 ASL P ; Shift (A P+1 P) to the left ROL P+1 ROL A INY ; Increment the scale factor in Y BNE DVL9 ; Loop up to DVL9 (this BNE is effectively a JMP, as Y ; will never be zero) .DV14 ; If we get here, A >= 64 and contains the highest byte ; of the numerator, scaled up by the number of left ; shifts in Y STA P+2 ; Store A in P+2, so we now have the scaled value of ; the numerator in P(2 1 0) LDA S ; Set A = |S| AND #%01111111 ;BMI DV9 ; This label is commented out in the original source .DVL6 ; We now shift (S R Q) left until bit 7 of S is set, ; reducing Y by the number of shifts. This makes the ; bottom part of the division as large as possible, thus ; retaining as much accuracy as we can. When we come to ; return the final result, we shift the result by the ; total number of places in Y, and in the correct ; direction, to give us the correct result ; ; We set A to |S| above, so the following actually ; shifts (A R Q) DEY ; Decrement the scale factor in Y ASL Q ; Shift (A R Q) to the left ROL R ROL A BPL DVL6 ; Loop up to DVL6 to do another shift, until bit 7 of A ; is set and we can't shift left any further .DV9 ; We have now shifted both the numerator and denominator ; left as far as they will go, keeping a tally of the ; overall scale factor of the various shifts in Y. We ; can now divide just the two highest bytes to get our ; result STA Q ; Set Q = A, the highest byte of the denominator LDA #254 ; Set R to have bits 1-7 set, so we can pass this to STA R ; LL31 to act as the bit counter in the division LDA P+2 ; Set A to the highest byte of the numerator .LL31new ASL A ; This contains the code from the LL31 routine, so BCS LL29new ; this section is exactly equivalent to a JSR LL31 CMP Q ; call, but is slightly faster as it's been inlined, BCC P%+4 ; so it calculates: SBC Q ; ROL R ; R = 256 * A / Q BCS LL31new ; = 256 * numerator / denominator JMP LL312new .LL29new SBC Q ; This is also part of the inline LL31 routine SEC ROL R BCS LL31new LDA R .LL312new ; The result of our division is now in R, so we just ; need to shift it back by the scale factor in Y LDA #0 ; Set K(3 2 1) = 0 to hold the result (we populate K STA K+1 ; next) STA K+2 STA K+3 TYA ; If Y is positive, jump to DV12 BPL DV12 ; If we get here then Y is negative, so we need to shift ; the result R to the left by Y places, and then set the ; correct sign for the result LDA R ; Set A = R .DVL8 ASL A ; Shift (K+3 K+2 K+1 A) left ROL K+1 ROL K+2 ROL K+3 INY ; Increment the scale factor in Y BNE DVL8 ; Loop back to DVL8 until we have shifted left by Y ; places STA K ; Store A in K so the result is now in K(3 2 1 0) LDA K+3 ; Set K+3 to the sign in T, which we set above to the ORA T ; correct sign for the result STA K+3 RTS ; Return from the subroutine .DV13 ; If we get here then Y is zero, so we don't need to ; shift the result R, we just need to set the correct ; sign for the result LDA R ; Store R in K so the result is now in K(3 2 1 0) STA K LDA T ; Set K+3 to the sign in T, which we set above to the STA K+3 ; correct sign for the result RTS ; Return from the subroutine .DV12 BEQ DV13 ; We jumped here having set A to the scale factor in Y, ; so this jumps up to DV13 if Y = 0 ; If we get here then Y is positive and non-zero, so we ; need to shift the result R to the right by Y places ; and then set the correct sign for the result. We also ; know that K(3 2 1) will stay 0, as we are shifting the ; lowest byte to the right, so no set bits will make ; their way into the top three bytes LDA R ; Set A = R .DVL10 LSR A ; Shift A right DEY ; Decrement the scale factor in Y BNE DVL10 ; Loop back to DVL10 until we have shifted right by Y ; places STA K ; Store the shifted A in K so the result is now in ; K(3 2 1 0) LDA T ; Set K+3 to the sign in T, which we set above to the STA K+3 ; correct sign for the result RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: cntr ; Type: Subroutine ; Category: Dashboard ; Summary: Apply damping to the pitch or roll dashboard indicator ; ; ------------------------------------------------------------------------------ ; ; Apply damping to the value in X, where X ranges from 1 to 255 with 128 as the ; centre point (so X represents a position on a centre-based dashboard slider, ; such as pitch or roll). If the value is in the left-hand side of the slider ; (1-127) then it bumps the value up by 1 so it moves towards the centre, and ; if it's in the right-hand side, it reduces it by 1, also moving it towards the ; centre. ; ; ****************************************************************************** .cntr LDA auto ; If the docking computer is currently activated, jump BNE cnt2 ; to cnt2 to skip the following as we always want to ; enable damping for the docking computer LDA DAMP ; If DAMP is non-zero, then keyboard damping is not BNE RE1 ; enabled, so jump to RE1 to return from the subroutine .cnt2 TXA ; If X < 128, then it's in the left-hand side of the BPL BUMP ; dashboard slider, so jump to BUMP to bump it up by 1, ; to move it closer to the centre DEX ; Otherwise X >= 128, so it's in the right-hand side BMI RE1 ; of the dashboard slider, so decrement X by 1, and if ; it's still >= 128, jump to RE1 to return from the ; subroutine, otherwise fall through to BUMP to undo ; the bump and then return .BUMP INX ; Bump X up by 1, and if it hasn't overshot the end of BNE RE1 ; the dashboard slider, jump to RE1 to return from the ; subroutine, otherwise fall through to REDU to drop ; it down by 1 again .REDU DEX ; Reduce X by 1, and if we have reached 0 jump up to BEQ BUMP ; BUMP to add 1, because we need the value to be in the ; range 1 to 255 .RE1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: BUMP2 ; Type: Subroutine ; Category: Dashboard ; Summary: Bump up the value of the pitch or roll dashboard indicator ; ; ------------------------------------------------------------------------------ ; ; Increase ("bump up") X by A, where X is either the current rate of pitch or ; the current rate of roll. ; ; The rate of pitch or roll ranges from 1 to 255 with 128 as the centre point. ; This is the amount by which the pitch or roll is currently changing, so 1 ; means it is decreasing at the maximum rate, 128 means it is not changing, ; and 255 means it is increasing at the maximum rate. These values correspond ; to the line on the DC or RL indicators on the dashboard, with 1 meaning full ; left, 128 meaning the middle, and 255 meaning full right. ; ; If bumping up X would push it past 255, then X is set to 255. ; ; If keyboard auto-recentre is configured and the result is less than 128, we ; bump X up to the mid-point, 128. This is the equivalent of having a roll or ; pitch in the left half of the indicator, when increasing the roll or pitch ; should jump us straight to the mid-point. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; RE2+2 Restore A from T and return from the subroutine ; ; ****************************************************************************** .BUMP2 STA T ; Store argument A in T so we can restore it later TXA ; Copy argument X into A CLC ; Clear the C flag so we can do addition without the ; C flag affecting the result ADC T ; Set X = A = argument X + argument A TAX BCC RE2 ; If the C flag is clear, then we didn't overflow, so ; jump to RE2 to auto-recentre and return the result LDX #255 ; We have an overflow, so set X to the maximum possible ; value of 255 .RE2 BPL djd1 ; If X has bit 7 clear (i.e. the result < 128), then ; jump to djd1 in routine REDU2 to do an auto-recentre, ; if configured, because the result is on the left side ; of the centre point of 128 ; Jumps to RE2+2 end up here LDA T ; Restore the original argument A from T into A RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: REDU2 ; Type: Subroutine ; Category: Dashboard ; Summary: Reduce the value of the pitch or roll dashboard indicator ; ; ------------------------------------------------------------------------------ ; ; Reduce X by A, where X is either the current rate of pitch or the current ; rate of roll. ; ; The rate of pitch or roll ranges from 1 to 255 with 128 as the centre point. ; This is the amount by which the pitch or roll is currently changing, so 1 ; means it is decreasing at the maximum rate, 128 means it is not changing, ; and 255 means it is increasing at the maximum rate. These values correspond ; to the line on the DC or RL indicators on the dashboard, with 1 meaning full ; left, 128 meaning the middle, and 255 meaning full right. ; ; If reducing X would bring it below 1, then X is set to 1. ; ; If keyboard auto-recentre is configured and the result is greater than 128, we ; reduce X down to the mid-point, 128. This is the equivalent of having a roll ; or pitch in the right half of the indicator, when decreasing the roll or pitch ; should jump us straight to the mid-point. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; djd1 Auto-recentre the value in X, if keyboard auto-recentre ; is configured ; ; ****************************************************************************** .REDU2 STA T ; Store argument A in T so we can restore it later TXA ; Copy argument X into A SEC ; Set the C flag so we can do subtraction without the ; C flag affecting the result SBC T ; Set X = A = argument X - argument A TAX BCS RE3 ; If the C flag is set, then we didn't underflow, so ; jump to RE3 to auto-recentre and return the result LDX #1 ; We have an underflow, so set X to the minimum possible ; value, 1 .RE3 BPL RE2+2 ; If X has bit 7 clear (i.e. the result < 128), then ; jump to RE2+2 above to return the result as is, ; because the result is on the left side of the centre ; point of 128, so we don't need to auto-centre .djd1 ; If we get here, then we need to apply auto-recentre, ; if it is configured LDA DJD ; If keyboard auto-recentre is disabled, then jump to BNE RE2+2 ; RE2+2 to restore A and return LDX #128 ; If we get here then keyboard auto-recentre is enabled, BMI RE2+2 ; so set X to 128 (the middle of our range) and jump to ; RE2+2 to restore A and return from the subroutine ; (this BMI is effectively a JMP as bit 7 of X is always ; set) ; ****************************************************************************** ; ; Name: ARCTAN ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Calculate A = arctan(P / Q) ; Deep dive: The sine, cosine and arctan tables ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; A = arctan(P / Q) ; ; In other words, this finds the angle in the right-angled triangle where the ; opposite side to angle A is length P and the adjacent side to angle A has ; length Q, so: ; ; tan(A) = P / Q ; ; The result in A is an integer representing the angle in radians. The routine ; returns values in the range 0 to 128, which covers 0 to 180 degrees (or 0 to ; PI radians). ; ; ****************************************************************************** .ARCTAN LDA P ; Set T1 = P EOR Q, which will have the sign of P * Q EOR Q ; ;AND #%10000000 ; The AND is commented out in the original source STA T1 LDA Q ; If Q = 0, jump to AR2 to return a right angle BEQ AR2 ASL A ; Set Q = |Q| * 2 (this is a quick way of clearing the STA Q ; sign bit, and we don't need to shift right again as we ; only ever use this value in the division with |P| * 2, ; which we set next) LDA P ; Set A = |P| * 2 ASL A CMP Q ; If A >= Q, i.e. |P| > |Q|, jump to AR1 to swap P BCS AR1 ; and Q around, so we can still use the lookup table JSR ARS1 ; Call ARS1 to set the following from the lookup table: ; ; A = arctan(A / Q) ; = arctan(|P / Q|) SEC ; Set the C flag so the SBC instruction in AR3 will be ; correct, should we jump there .AR4 LDX T1 ; If T1 is negative, i.e. P and Q have different signs, BMI AR3 ; jump down to AR3 to return arctan(-|P / Q|) RTS ; Otherwise P and Q have the same sign, so our result is ; correct and we can return from the subroutine .AR1 ; We want to calculate arctan(t) where |t| > 1, so we ; can use the calculation described in the documentation ; for the ACT table, i.e. 64 - arctan(1 / t) LDX Q ; Swap the values in Q and P, using the fact that we STA Q ; called AR1 with A = P STX P ; TXA ; This also sets A = P (which now contains the original ; argument |Q|) JSR ARS1 ; Call ARS1 to set the following from the lookup table: ; ; A = arctan(A / Q) ; = arctan(|Q / P|) ; = arctan(1 / |P / Q|) STA T ; Set T = 64 - T LDA #64 SBC T BCS AR4 ; Jump to AR4 to continue the calculation (this BCS is ; effectively a JMP as the subtraction will never ; underflow, as ARS1 returns values in the range 0-31) .AR2 ; If we get here then Q = 0, so tan(A) = infinity and ; A is a right angle, or 0.25 of a circle. We allocate ; 255 to a full circle, so we should return 63 for a ; right angle LDA #63 ; Set A to 63, to represent a right angle RTS ; Return from the subroutine .AR3 ; A contains arctan(|P / Q|) but P and Q have different ; signs, so we need to return arctan(-|P / Q|), using ; the calculation described in the documentation for the ; ACT table, i.e. 128 - A STA T ; Set A = 128 - A LDA #128 ; ;SEC ; The SEC instruction is commented out in the original SBC T ; source, and isn't required as we did a SEC before ; calling AR3 RTS ; Return from the subroutine .ARS1 ; This routine fetches arctan(A / Q) from the ACT table, ; so A will be set to an integer in the range 0 to 31 ; that represents an angle from 0 to 45 degrees (or 0 to ; PI / 4 radians) JSR LL28 ; Call LL28 to calculate: ; ; R = 256 * A / Q LDA R ; Set X = R / 8 LSR A ; = 32 * A / Q LSR A ; LSR A ; so X has the value t * 32 where t = A / Q, which is TAX ; what we need to look up values in the ACT table LDA ACT,X ; Fetch ACT+X from the ACT table into A, so now: ; ; A = value in ACT + X ; = value in ACT + (32 * A / Q) ; = arctan(A / Q) RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LASLI ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw the laser lines for when we fire our lasers ; ; ------------------------------------------------------------------------------ ; ; Draw the laser lines, aiming them to slightly different place each time so ; they appear to flicker and dance. Also heat up the laser temperature and drain ; some energy. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; LASLI2 Just draw the current laser lines without moving the ; centre point, draining energy or heating up. This has ; the effect of removing the lines from the screen ; ; LASLI-1 Contains an RTS ; ; ****************************************************************************** .LASLI JSR DORND ; Set A and X to random numbers AND #7 ; Restrict A to a random value in the range 0 to 7 ADC #Y-4 ; Set LASY to four pixels above the centre of the STA LASY ; screen (#Y), plus our random number, so the laser ; dances above and below the centre point JSR DORND ; Set A and X to random numbers AND #7 ; Restrict A to a random value in the range 0 to 7 ADC #X-4 ; Set LASX to four pixels left of the centre of the STA LASX ; screen (#X), plus our random number, so the laser ; dances to the left and right of the centre point LDA GNTMP ; Add 8 to the laser temperature in GNTMP ADC #8 STA GNTMP JSR DENGY ; Call DENGY to deplete our energy banks by 1 .LASLI2 LDA QQ11 ; If this is not a space view (i.e. QQ11 is non-zero) BNE LASLI-1 ; then jump to MA9 to return from the main flight loop ; (as LASLI-1 is an RTS) ;LDA #RED ; These instructions are commented out in the original ;STA COL ; source; they would switch to colour 2, which is red in ; the space view LDA #32 ; Set A = 32 and Y = 224 for the first set of laser LDY #224 ; lines (the wider pair of lines) JSR las ; Call las below to draw the first set of laser lines LDA #48 ; Fall through into las with A = 48 and Y = 208 to draw LDY #208 ; a second set of lines (the narrower pair) ; The following routine draws two laser lines, one from ; the centre point down to point A on the bottom row, ; and the other from the centre point down to point Y ; on the bottom row. We therefore get lines from the ; centre point to points 32, 48, 208 and 224 along the ; bottom row, giving us the triangular laser effect ; we're after .las STA X2 ; Set X2 = A LDA LASX ; Set (X1, Y1) to the random centre point we set above STA X1 LDA LASY STA Y1 LDA #2*Y-1 ; Set Y2 = 2 * #Y - 1. The constant #Y is 96, the STA Y2 ; y-coordinate of the mid-point of the space view, so ; this sets Y2 to 191, the y-coordinate of the bottom ; pixel row of the space view JSR LL30 ; Draw a line from (X1, Y1) to (X2, Y2), so that's from ; the centre point to (A, 191) LDA LASX ; Set (X1, Y1) to the random centre point we set above STA X1 LDA LASY STA Y1 STY X2 ; Set X2 = Y LDA #2*Y-1 ; Set Y2 = 2 * #Y - 1, the y-coordinate of the bottom STA Y2 ; pixel row of the space view (as before) JMP LL30 ; Draw a line from (X1, Y1) to (X2, Y2), so that's from ; the centre point to (Y, 191), and return from ; the subroutine using a tail call ; ****************************************************************************** ; ; Name: PDESC ; Type: Subroutine ; Category: Universe ; Summary: Print the system's extended description or a mission 1 directive ; Deep dive: Extended system descriptions ; Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; This prints a specific system's extended description. This is called the "pink ; volcanoes string" in a comment in the original source, and the "goat soup" ; recipe by Ian Bell on his website (where he also refers to the species string ; as the "pink felines" string). ; ; For some special systems, when you are docked at them, the procedurally ; generated extended description is overridden and a text token from the RUTOK ; table is shown instead. If mission 1 is in progress, then a number of systems ; along the route of that mission's story will show custom mission-related ; directives in place of that system's normal "goat soup" phrase. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; ZZ The system number (0-255) ; ; ****************************************************************************** .PDESC LDA QQ8 ; If either byte in QQ18(1 0) is non-zero, meaning that ORA QQ8+1 ; the distance from the current system to the selected BNE PD1 ; is non-zero, jump to PD1 to show the standard "goat ; soup" description LDA QQ12 ; If QQ12 does not have bit 7 set, which means we are BPL PD1 ; not docked, jump to PD1 to show the standard "goat ; soup" description ; If we get here, then the current system is the same as ; the selected system and we are docked, so now to check ; whether there is a special override token for this ; system LDY #NRU% ; Set Y as a loop counter as we work our way through the ; system numbers in RUPLA, starting at NRU% (which is ; the number of entries in RUPLA, 26) and working our ; way down to 1 .PDL1 LDA RUPLA-1,Y ; Fetch the Y-th byte from RUPLA-1 into A (we use ; RUPLA-1 because Y is looping from 26 to 1) CMP ZZ ; If A doesn't match the system whose description we BNE PD2 ; are printing (in ZZ), jump to PD2 to keep looping ; through the system numbers in RUPLA ; If we get here we have found a match for this system ; number in RUPLA LDA RUGAL-1,Y ; Fetch the Y-th byte from RUGAL-1 into A AND #%01111111 ; Extract bits 0-6 of A CMP GCNT ; If the result does not equal the current galaxy BNE PD2 ; number, jump to PD2 to keep looping through the system ; numbers in RUPLA LDA RUGAL-1,Y ; Fetch the Y-th byte from RUGAL-1 into A, once again BMI PD3 ; If bit 7 is set, jump to PD3 to print the extended ; token in A from the second table in RUTOK LDA TP ; Fetch bit 0 of TP into the C flag, and skip to PD1 if LSR A ; it is clear (i.e. if mission 1 is not in progress) to BCC PD1 ; print the "goat soup" extended description ; If we get here then mission 1 is in progress, so we ; print out the corresponding token from RUTOK JSR MT14 ; Call MT14 to switch to justified text LDA #1 ; Set A = 1 so that extended token 1 (an empty string) ; gets printed below instead of token 176, followed by ; the Y-th token in RUTOK EQUB $2C ; Skip the next instruction by turning it into ; $2C $A9 $B0, or BIT $B0A9, which does nothing apart ; from affect the flags .PD3 LDA #176 ; Print extended token 176 ("{lower case}{justify} JSR DETOK2 ; {single cap}") TYA ; Print the extended token in Y from the second table JSR DETOK3 ; in RUTOK LDA #177 ; Set A = 177 so when we jump to PD4 in the next ; instruction, we print token 177 (".{cr}{left align}") BNE PD4 ; Jump to PD4 to print the extended token in A and ; return from the subroutine using a tail call .PD2 DEY ; Decrement the byte counter in Y BNE PDL1 ; Loop back to check the next byte in RUPLA until we ; either find a match for the system in ZZ, or we fall ; through into the "goat soup" extended description ; routine .PD1 ; We now print the "goat soup" extended description LDX #3 ; We now want to seed the random number generator with ; the s1 and s2 16-bit seeds from the current system, so ; we get the same extended description for each system ; every time we call PDESC, so set a counter in X for ; copying 4 bytes .PDL1K ; This label is a duplicate of the label above ; ; In the original source this label is PDL1, but ; because BeebAsm doesn't allow us to redefine labels, ; I have renamed it to PDL1K LDA QQ15+2,X ; Copy QQ15+2 to QQ15+5 (s1 and s2) to RAND to RAND+3 STA RAND,X DEX ; Decrement the loop counter BPL PDL1K ; Loop back to PDL1K until we have copied all LDA #5 ; Set A = 5, so we print extended token 5 in the next ; instruction ("{lower case}{justify}{single cap}[86-90] ; IS [140-144].{cr}{left align}" .PD4 JMP DETOK ; Print the extended token given in A, and return from ; the subroutine using a tail call ; ****************************************************************************** ; ; Name: BRIEF2 ; Type: Subroutine ; Category: Missions ; Summary: Start mission 2 ; Deep dive: The Thargoid Plans mission ; ; ****************************************************************************** .BRIEF2 LDA TP ; Set bit 2 of TP to indicate mission 2 is in progress ORA #%00000100 ; but plans have not yet been picked up STA TP LDA #11 ; Set A = 11 so the call to BRP prints extended token 11 ; (the initial contact at the start of mission 2, asking ; us to head for Ceerdi for a mission briefing) ; Fall through into BRP to print the extended token in A ; and show the Status Mode screen ; ****************************************************************************** ; ; Name: BRP ; Type: Subroutine ; Category: Missions ; Summary: Print an extended token and show the Status Mode screen ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; BAYSTEP Go to the docking bay (i.e. show the Status Mode screen) ; ; ****************************************************************************** .BRP JSR DETOK ; Print the extended token in A .BAYSTEP JMP BAY ; Jump to BAY to go to the docking bay (i.e. show the ; Status Mode screen) and return from the subroutine ; using a tail call ; ****************************************************************************** ; ; Name: BRIEF3 ; Type: Subroutine ; Category: Missions ; Summary: Receive the briefing and plans for mission 2 ; Deep dive: The Thargoid Plans mission ; ; ****************************************************************************** .BRIEF3 LDA TP ; Set bits 1 and 3 of TP to indicate that mission 1 is AND #%11110000 ; complete, and mission 2 is in progress and the plans ORA #%00001010 ; have been picked up STA TP LDA #222 ; Set A = 222 so the call to BRP prints extended token ; 222 (the briefing for mission 2 where we pick up the ; plans we need to take to Birera) BNE BRP ; Jump to BRP to print the extended token in A and show ; the Status Mode screen), returning from the subroutine ; using a tail call (this BNE is effectively a JMP as A ; is never zero) ; ****************************************************************************** ; ; Name: DEBRIEF2 ; Type: Subroutine ; Category: Missions ; Summary: Finish mission 2 ; Deep dive: The Thargoid Plans mission ; ; ****************************************************************************** .DEBRIEF2 LDA TP ; Set bit 2 of TP to indicate mission 2 is complete (so ORA #%00000100 ; both bits 2 and 3 are now set) STA TP LDA #2 ; Set ENGY to 2 so our energy banks recharge at a faster STA ENGY ; rate, as our mission reward is a special navy energy ; unit that recharges at a rate of 3 units of energy on ; each iteration of the main loop, compared to a rate of ; 2 units of energy for the standard energy unit INC TALLY+1 ; Award 256 kill points for completing the mission LDA #223 ; Set A = 223 so the call to BRP prints extended token ; 223 (the thank you message at the end of mission 2) BNE BRP ; Jump to BRP to print the extended token in A and show ; the Status Mode screen), returning from the subroutine ; using a tail call (this BNE is effectively a JMP as A ; is never zero) ; ****************************************************************************** ; ; Name: DEBRIEF ; Type: Subroutine ; Category: Missions ; Summary: Finish mission 1 ; Deep dive: The Constrictor mission ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; BRPS Print the extended token in A, show the Status Mode ; screen and return from the subroutine ; ; ****************************************************************************** .DEBRIEF LSR TP ; Clear bit 0 of TP to indicate that mission 1 is no ASL TP ; longer in progress, as we have completed it ;INC TALLY+1 ; This instruction is commented out in the original ; source LDX #LO(50000) ; Increase our cash reserves by the generous mission LDY #HI(50000) ; reward of 5,000 CR JSR MCASH LDA #15 ; Set A = 15 so the call to BRP prints extended token 15 ; (the thank you message at the end of mission 1) .BRPS BNE BRP ; Jump to BRP to print the extended token in A and show ; the Status Mode screen, returning from the subroutine ; using a tail call (this BNE is effectively a JMP as A ; is never zero) ; ****************************************************************************** ; ; Name: TBRIEF ; Type: Subroutine ; Category: Missions ; Summary: Start mission 3 ; Deep dive: The Trumbles mission ; ; ****************************************************************************** .TBRIEF LDA TP ; Set bit 4 of TP to indicate that mission 3 has been ORA #%00010000 ; triggered STA TP LDA #199 ; Print extended token 199, which is the briefing for JSR DETOK ; the Trumbles mission JSR YESNO ; Call YESNO to wait until either "Y" or "N" is pressed BCC BAYSTEP ; If "N" was pressed, then the mission was not accepted, ; jump to BAYSTEP to go to the docking bay (i.e. show ; the Status Mode screen) LDY #HI(50000) ; Otherwise the mission was accepted, so subtract LDX #LO(50000) ; 50,000 CR from the cash pot to pay for the Trumble JSR LCASH INC TRIBBLE ; Increment the number of Trumbles from 0 to 1, so they ; start breeding JMP BAY ; Go to the docking bay (i.e. show the Status Mode ; screen) ; ****************************************************************************** ; ; Name: BRIEF ; Type: Subroutine ; Category: Missions ; Summary: Start mission 1 and show the mission briefing ; Deep dive: The Constrictor mission ; ; ------------------------------------------------------------------------------ ; ; This routine does the following: ; ; * Clear the screen ; * Display "INCOMING MESSAGE" in the middle of the screen ; * Wait for 2 seconds ; * Clear the screen ; * Show the Constrictor rolling and pitching in the middle of the screen ; * Do this for 64 loop iterations ; * Move the ship away from us and up until it's near the top of the screen ; * Show the mission 1 briefing in extended token 10 ; ; The mission briefing ends with a "{display ship, wait for key press}" token, ; which calls the PAUSE routine. This continues to display the rotating ship, ; waiting until a key is pressed, and then removes the ship from the screen. ; ; ****************************************************************************** .BRIEF LSR TP ; Set bit 0 of TP to indicate that mission 1 is now in SEC ; progress ROL TP JSR BRIS ; Call BRIS to clear the screen, display "INCOMING ; MESSAGE" and wait for 2 seconds JSR ZINF ; Call ZINF to reset the INWK ship workspace LDA #CON ; Set the ship type in TYPE to the Constrictor STA TYPE JSR NWSHP ; Add a new Constrictor to the local bubble (in this ; case, the briefing screen) LDA #1 ; Move the text cursor to column 1 JSR DOXC STA INWK+7 ; Set z_hi = 1, the distance at which we show the ; rotating ship JSR TT66 ; Clear the top part of the screen, draw a border box, ; and set the current view type in QQ11 to 1 LDA #64 ; Set the main loop counter to 64, so the ship rotates STA MCNT ; for 64 iterations through MVEIT .BRL1 LDX #%01111111 ; Set the ship's roll counter to a positive roll that STX INWK+29 ; doesn't dampen (a clockwise roll) STX INWK+30 ; Set the ship's pitch counter to a positive pitch that ; doesn't dampen (a diving pitch) JSR LL9 ; Draw the ship on screen JSR MVEIT ; Call MVEIT to rotate the ship in space DEC MCNT ; Decrease the counter in MCNT BNE BRL1 ; Loop back to keep moving the ship until we have done ; all 64 iterations .BRL2 LSR INWK ; Halve x_lo so the Constrictor moves towards the centre INC INWK+6 ; Increment z_lo so the Constrictor moves away from us BEQ BR2 ; If z_lo = 0 (i.e. it just went past 255), jump to BR2 ; to show the briefing INC INWK+6 ; Increment z_lo so the Constrictor moves a bit further ; away from us BEQ BR2 ; If z_lo = 0 (i.e. it just went past 255), jump out of ; the loop to BR2 to stop moving the ship up the screen ; and show the briefing LDX INWK+3 ; Set X = y_lo + 1 INX CPX #conhieght ; If X < conhieght then skip the next instruction BCC P%+4 LDX #conhieght ; X is bigger than conhieght, so set X = conhieght so ; that X has a maximum value of conhieght STX INWK+3 ; Set y_lo = X ; = y_lo + 1 ; ; so the ship moves up the screen (as space coordinates ; have the y-axis going up) JSR LL9 ; Draw the ship on screen JSR MVEIT ; Call MVEIT to move and rotate the ship in space DEC MCNT ; Decrease the counter in MCNT JMP BRL2 ; Loop back to keep moving the ship up the screen and ; away from us .BR2 INC INWK+7 ; Increment z_hi, to keep the ship at the same distance ; as we just incremented z_lo past 255 LDA #10 ; Set A = 10 so the call to BRP prints extended token 10 ; (the briefing for mission 1 where we find out all ; about the stolen Constrictor) BNE BRPS ; Jump to BRP via BRPS to print the extended token in A ; and show the Status Mode screen, returning from the ; subroutine using a tail call (this BNE is effectively ; a JMP as A is never zero) ; ****************************************************************************** ; ; Name: BRIS ; Type: Subroutine ; Category: Missions ; Summary: Clear the screen, display "INCOMING MESSAGE" and wait for 2 ; seconds ; ; ****************************************************************************** .BRIS LDA #216 ; Print extended token 216 ("{clear screen}{tab 6}{move JSR DETOK ; to row 10, white, lower case}{white}{all caps}INCOMING ; MESSAGE" LDY #100 ; Wait for 100/50 of a second (2 seconds) on PAL JMP DELAY ; systems, or 100/60 of a second (1.67 seconds) on NTSC, ; and return from the subroutine using a tail call ; ****************************************************************************** ; ; Name: PAUSE ; Type: Subroutine ; Category: Missions ; Summary: Display a rotating ship, waiting until a key is pressed, then ; remove the ship from the screen ; ; ****************************************************************************** .PAUSE JSR PAS1 ; Call PAS1 to display the rotating ship at space ; coordinates (0, 112, 256) and scan the keyboard, ; returning the internal key number in X (or 0 for no ; key press) BNE PAUSE ; If a key was already being held down when we entered ; this routine, keep looping back up to PAUSE, until ; the key is released .PAL1 JSR PAS1 ; Call PAS1 to display the rotating ship at space ; coordinates (0, 112, 256) and scan the keyboard, ; returning the internal key number in X (or 0 for no ; key press) BEQ PAL1 ; Keep looping up to PAL1 until a key is pressed LDA #0 ; Set the ship's AI flag to 0 (no AI) so it doesn't get STA INWK+31 ; any ideas of its own LDA #1 ; Clear the top part of the screen, draw a border box, JSR TT66 ; and set the current view type in QQ11 to 1 JSR LL9 ; Draw the ship on screen to redisplay it ; Fall through into MT23 to move to row 10, switch to ; white text, and switch to lower case when printing ; extended tokens ; ****************************************************************************** ; ; Name: MT23 ; Type: Subroutine ; Category: Text ; Summary: Move to row 10, switch to white text, and switch to lower case ; when printing extended tokens ; Deep dive: Extended text tokens ; ; ****************************************************************************** .MT23 LDA #10 ; Set A = 10, so when we fall through into MT29, the ; text cursor gets moved to row 10 EQUB $2C ; Skip the next instruction by turning it into ; $2C $A9 $06, or BIT $06A9, which does nothing apart ; from affect the flags ; Fall through into MT29 to move to the row in A, switch ; to white text, and switch to lower case ; ****************************************************************************** ; ; Name: MT29 ; Type: Subroutine ; Category: Text ; Summary: Move to row 6 and switch to lower case when printing extended ; tokens ; Deep dive: Extended text tokens ; ; ****************************************************************************** .MT29 LDA #6 ; Move the text cursor to row 6 JSR DOYC JSR WHITETEXT ; This routine has no effect in this version of Elite JMP MT13 ; Jump to MT13 to set bit 7 of DTW6 and bit 5 of DTW1, ; returning from the subroutine using a tail call ; ****************************************************************************** ; ; Name: PAS1 ; Type: Subroutine ; Category: Missions ; Summary: Display a rotating ship at space coordinates (0, conhieght, 256) ; and scan the keyboard ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; X If a key is being pressed, X contains the ASCII code of ; the key being pressed, otherwise it contains 0 ; ; A Contains the same as X ; ; ****************************************************************************** .PAS1 LDA #conhieght ; Set y_lo = conhieght STA INWK+3 LDA #0 ; Set x_lo = 0 STA INWK STA INWK+6 ; Set z_lo = 0 LDA #2 ; Set z_hi = 1, so (z_hi z_lo) = 256 STA INWK+7 JSR LL9 ; Draw the ship on screen JSR MVEIT ; Call MVEIT to move and rotate the ship in space JMP RDKEY ; Scan the keyboard for a key press and return the ; internal key number in X (or 0 for no key press), ; returning from the subroutine using a tail call ; ****************************************************************************** ; ; Name: PAUSE2 ; Type: Subroutine ; Category: Keyboard ; Summary: Wait until a key is pressed, ignoring any existing key press ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; X The ASCII code of the key that was pressed ; ; ****************************************************************************** .PAUSE2 JSR RDKEY ; Scan the keyboard for a key press and return the ; internal key number in A and X (or 0 for no key press) BNE PAUSE2 ; If a key was already being held down when we entered ; this routine, keep looping back up to PAUSE2, until ; the key is released JSR RDKEY ; Any pre-existing key press is now gone, so we can ; start scanning the keyboard again, returning the ; internal key number in A and X (or 0 for no key press) BEQ PAUSE2 ; Keep looping up to PAUSE2 until a key is pressed .newyearseve RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: GINF ; Type: Subroutine ; Category: Universe ; Summary: Fetch the address of a ship's data block into INF ; ; ------------------------------------------------------------------------------ ; ; Get the address of the data block for ship slot X and store it in INF. This ; address is fetched from the UNIV table, which stores the addresses of the 13 ; ship data blocks in workspace K%. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The ship slot number for which we want the data block ; address ; ; ****************************************************************************** .GINF TXA ; Set Y = X * 2 ASL A TAY LDA UNIV,Y ; Get the high byte of the address of the X-th ship STA INF ; from UNIV and store it in INF LDA UNIV+1,Y ; Get the low byte of the address of the X-th ship STA INF+1 ; from UNIV and store it in INF RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: ping ; Type: Subroutine ; Category: Universe ; Summary: Set the selected system to the current system ; ; ****************************************************************************** .ping LDX #1 ; We want to copy the X- and Y-coordinates of the ; current system in (QQ0, QQ1) to the selected system's ; coordinates in (QQ9, QQ10), so set up a counter to ; copy two bytes .pl1 LDA QQ0,X ; Load byte X from the current system in QQ0/QQ1 STA QQ9,X ; Store byte X in the selected system in QQ9/QQ10 DEX ; Decrement the loop counter BPL pl1 ; Loop back for the next byte to copy RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DELAY ; Type: Subroutine ; Category: Utility routines ; Summary: Wait for a specified time, in either 1/50s of a second (on PAL ; systems) or 1/60s of a second (on NTSC systems) ; ; ------------------------------------------------------------------------------ ; ; Wait for the number of vertical syncs given in Y, so this effectively waits ; for Y/50 of a second on PAL systems (as the PAL vertical sync occurs 50 times ; a second) or Y/60 of a second on NTSC systems (as the NTSC vertical sync ; occurs 60 times a second). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The number of vertical sync events to wait for ; ; ****************************************************************************** .DELAY JSR WSCAN ; Call WSCAN to wait for the vertical sync, so the whole ; screen gets drawn DEY ; Decrement the counter in Y BNE DELAY ; If Y isn't yet at zero, jump back to DELAY to wait ; for another vertical sync RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: sightcol ; Type: Variable ; Category: Drawing lines ; Summary: Colours for the crosshair sights on the different laser types ; Deep dive: Sprite usage in Commodore 64 Elite ; ; ****************************************************************************** .sightcol EQUB 7 ; Pulse lasers have yellow sights EQUB 7 ; Beam lasers have yellow sights EQUB 13 ; Military lasers have light green sights EQUB 4 ; Mining lasers have purple sights ; ****************************************************************************** ; ; Name: MTIN ; Type: Variable ; Category: Text ; Summary: Lookup table for random tokens in the extended token table (0-37) ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; The ERND token type, which is part of the extended token system, takes an ; argument between 0 and 37, and returns a randomly chosen token in the range ; specified in this table. This is used to generate the extended description of ; each system. ; ; For example, the entry at position 13 in this table (counting from 0) is 66, ; so ERND 14 will expand into a random token in the range 66-70, i.e. one of ; "JUICE", "BRANDY", "WATER", "BREW" and "GARGLE BLASTERS". ; ; ****************************************************************************** .MTIN EQUB 16 ; Token 0: a random extended token between 16 and 20 EQUB 21 ; Token 1: a random extended token between 21 and 25 EQUB 26 ; Token 2: a random extended token between 26 and 30 EQUB 31 ; Token 3: a random extended token between 31 and 35 EQUB 155 ; Token 4: a random extended token between 155 and 159 EQUB 160 ; Token 5: a random extended token between 160 and 164 EQUB 46 ; Token 6: a random extended token between 46 and 50 EQUB 165 ; Token 7: a random extended token between 165 and 169 EQUB 36 ; Token 8: a random extended token between 36 and 40 EQUB 41 ; Token 9: a random extended token between 41 and 45 EQUB 61 ; Token 10: a random extended token between 61 and 65 EQUB 51 ; Token 11: a random extended token between 51 and 55 EQUB 56 ; Token 12: a random extended token between 56 and 60 EQUB 170 ; Token 13: a random extended token between 170 and 174 EQUB 66 ; Token 14: a random extended token between 66 and 70 EQUB 71 ; Token 15: a random extended token between 71 and 75 EQUB 76 ; Token 16: a random extended token between 76 and 80 EQUB 81 ; Token 17: a random extended token between 81 and 85 EQUB 86 ; Token 18: a random extended token between 86 and 90 EQUB 140 ; Token 19: a random extended token between 140 and 144 EQUB 96 ; Token 20: a random extended token between 96 and 100 EQUB 101 ; Token 21: a random extended token between 101 and 105 EQUB 135 ; Token 22: a random extended token between 135 and 139 EQUB 130 ; Token 23: a random extended token between 130 and 134 EQUB 91 ; Token 24: a random extended token between 91 and 95 EQUB 106 ; Token 25: a random extended token between 106 and 110 EQUB 180 ; Token 26: a random extended token between 180 and 184 EQUB 185 ; Token 27: a random extended token between 185 and 189 EQUB 190 ; Token 28: a random extended token between 190 and 194 EQUB 225 ; Token 29: a random extended token between 225 and 229 EQUB 230 ; Token 30: a random extended token between 230 and 234 EQUB 235 ; Token 31: a random extended token between 235 and 239 EQUB 240 ; Token 32: a random extended token between 240 and 244 EQUB 245 ; Token 33: a random extended token between 245 and 249 EQUB 250 ; Token 34: a random extended token between 250 and 254 EQUB 115 ; Token 35: a random extended token between 115 and 119 EQUB 120 ; Token 36: a random extended token between 120 and 124 EQUB 125 ; Token 37: a random extended token between 125 and 129 ; ****************************************************************************** ; ; Name: R% ; Type: Variable ; Category: Utility routines ; Summary: Denotes the end of the first part of the main game code (CODE1), ; from ELITE A to ELITE C ; ; ****************************************************************************** .R% SKIP 0 ASSERT R% < SCBASE ; Make sure that CODE1 doesn't spill over into screen ; memory at SCBASE ; ****************************************************************************** ; ; Save ELTC.bin ; ; ****************************************************************************** PRINT "ELITE C" PRINT "Assembled at ", ~CODE_C% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_C%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_C% PRINT "S.ELTC ", ~CODE_C%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_C% SAVE "3-assembled-output/ELTC.bin", CODE_C%, P%, LOAD% ; ****************************************************************************** ; ; ELITE D FILE ; ; Produces the binary file ELTD.bin that gets loaded by elite-checksum.py. ; ; ****************************************************************************** ORG C% ; Set the assembly address for the second block of game ; code (ELITE D onwards), which is defined in C% CODE_D% = P% LOAD_D% = LOAD% + P% - CODE% ; ****************************************************************************** ; ; Name: tnpr1 ; Type: Subroutine ; Category: Market ; Summary: Work out if we have space for one tonne of cargo ; ; ------------------------------------------------------------------------------ ; ; Given a market item, work out whether there is room in the cargo hold for one ; tonne of this item. ; ; For standard tonne canisters, the limit is given by the type of cargo hold we ; have, with a standard cargo hold having a capacity of 20t and an extended ; cargo bay being 35t. ; ; For items measured in kg (gold, platinum), g (gem-stones) and alien items, ; the individual limit on each of these is 200 units. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The type of market item (see QQ23 for a list of market ; item numbers) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A = 1 ; ; C flag Returns the result: ; ; * Set if there is no room for this item ; ; * Clear if there is room for this item ; ; ****************************************************************************** .tnpr1 STA QQ29 ; Store the type of market item in QQ29 LDA #1 ; Set the number of units of this market item to 1 ; Fall through into tnpr to work out whether there is ; room in the cargo hold for A tonnes of the item of ; type QQ29 ; ****************************************************************************** ; ; Name: tnpr ; Type: Subroutine ; Category: Market ; Summary: Work out if we have space for a specific amount of cargo ; ; ------------------------------------------------------------------------------ ; ; Given a market item and an amount, work out whether there is room in the ; cargo hold for this item. ; ; For standard tonne canisters, the limit is given by the type of cargo hold we ; have, with a standard cargo hold having a capacity of 20t and an extended ; cargo bay being 35t. ; ; For items measured in kg (gold, platinum), g (gem-stones) and alien items, ; the individual limit on each of these is 200 units. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The number of units of this market item ; ; QQ29 The type of market item (see QQ23 for a list of market ; item numbers) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A is preserved ; ; C flag Returns the result: ; ; * Set if there is no room for this item ; ; * Clear if there is room for this item ; ; ****************************************************************************** .tnpr PHA ; Store A on the stack LDX #12 ; If QQ29 > 12 then jump to kg below, as this cargo CPX QQ29 ; type is gold, platinum, gem-stones or alien items, BCC kg ; and they have different cargo limits to the standard ; tonne canisters .Tml ; Here we count the tonne canisters we have in the hold ; and add to A to see if we have enough room for A more ; tonnes of cargo, using X as the loop counter, starting ; with X = 12 ADC QQ20,X ; Set A = A + the number of tonnes we have in the hold ; of market item number X. Note that the first time we ; go round this loop, the C flag is set (as we didn't ; branch with the BCC above, so the effect of this loop ; is to count the number of tonne canisters in the hold, ; and add 1 DEX ; Decrement the loop counter BPL Tml ; Loop back to add in the next market item in the hold, ; until we have added up all market items from 12 ; (minerals) down to 0 (food) ADC TRIBBLE+1 ; Add the high byte of the number of Trumbles in the ; hold, as 256 Trumbles take up one tonne of cargo space CMP CRGO ; If A < CRGO then the C flag will be clear (we have ; room in the hold) ; ; If A >= CRGO then the C flag will be set (we do not ; have room in the hold) ; ; This works because A contains the number of canisters ; plus 1, while CRGO contains our cargo capacity plus 2, ; so if we actually have "a" canisters and a capacity ; of "c", then: ; ; A < CRGO means: a+1 < c+2 ; a < c+1 ; a <= c ; ; So this is why the value in CRGO is 2 higher than the ; actual cargo bay size, i.e. it's 22 for the standard ; 20-tonne bay, and 37 for the large 35-tonne bay PLA ; Restore A from the stack RTS ; Return from the subroutine .kg ; Here we count the number of items of this type that ; we already have in the hold, and add to A to see if ; we have enough room for A more units LDY QQ29 ; Set Y to the item number we want to add ADC QQ20,Y ; Set A = A + the number of units of this item that we ; already have in the hold CMP #200 ; Is the result greater than 200 (the limit on ; individual stocks of gold, platinum, gem-stones and ; alien items)? ; ; If so, this sets the C flag (no room) ; ; Otherwise it is clear (we have room) PLA ; Restore A from the stack RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DOXC ; Type: Subroutine ; Category: Text ; Summary: Move the text cursor to a specific column ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text column ; ; ****************************************************************************** .DOXC STA XC ; Store the new text column in XC RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DOYC ; Type: Subroutine ; Category: Text ; Summary: Move the text cursor to a specific row ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text row ; ; ****************************************************************************** .DOYC STA YC ; Store the new text row in YC RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: INCYC ; Type: Subroutine ; Category: Text ; Summary: Move the text cursor to the next row ; ; ****************************************************************************** .INCYC INC YC ; Move the text cursor to the next row RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DOVDU19 ; Type: Subroutine ; Category: Drawing the screen ; Summary: Implement the #SETVDU19 command (change mode 1 palette) ; ; ------------------------------------------------------------------------------ ; ; This subroutine has no effect in this version of Elite. ; ; ****************************************************************************** .DOVDU19 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TRADEMODE ; Type: Subroutine ; Category: Drawing the screen ; Summary: Clear the screen and set up a trading screen ; ; ------------------------------------------------------------------------------ ; ; Clear the top part of the screen, draw a border and set the current view ; type in QQ11 to A. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The type of the new current view (see QQ11 for a list of ; view types) ; ; ****************************************************************************** .TRADEMODE JSR TT66 ; Clear the top part of the screen, draw a border box, ; and set the current view type in QQ11 to A JSR FLKB ; Call FLKB to flush the keyboard buffer LDA #48 ; Switch to the palette for trading screens, though this JSR DOVDU19 ; doesn't actually do anything in this version of Elite ;LDA #CYAN ; These instructions are commented out in the original ;JMP DOCOL ; source RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TT20 ; Type: Subroutine ; Category: Universe ; Summary: Twist the selected system's seeds four times ; Deep dive: Twisting the system seeds ; Galaxy and system seeds ; ; ------------------------------------------------------------------------------ ; ; Twist the three 16-bit seeds in QQ15 (selected system) four times, to ; generate the next system. ; ; ****************************************************************************** .TT20 JSR P%+3 ; This line calls the line below as a subroutine, which ; does two twists before returning here, and then we ; fall through to the line below for another two ; twists, so the net effect of these two consecutive ; JSR calls is four twists, not counting the ones ; inside your head as you try to follow this process JSR P%+3 ; This line calls TT54 as a subroutine to do a twist, ; and then falls through into TT54 to do another twist ; before returning from the subroutine ; ****************************************************************************** ; ; Name: TT54 ; Type: Subroutine ; Category: Universe ; Summary: Twist the selected system's seeds ; Deep dive: Twisting the system seeds ; Galaxy and system seeds ; ; ------------------------------------------------------------------------------ ; ; This routine twists the three 16-bit seeds in QQ15 once. ; ; If we start with seeds s0, s1 and s2 and we want to work out their new values ; after we perform a twist (let's call the new values s0´, s1´ and s2´), then: ; ; s0´ = s1 ; s1´ = s2 ; s2´ = s0 + s1 + s2 ; ; So given an existing set of seeds in s0, s1 and s2, we can get the new values ; s0´, s1´ and s2´ simply by doing the above sums. And if we want to do the ; above in-place without creating three new s´ variables, then we can do the ; following: ; ; tmp = s0 + s1 ; s0 = s1 ; s1 = s2 ; s2 = tmp + s1 ; ; So this is what we do in this routine, where each seed is a 16-bit number. ; ; ****************************************************************************** .TT54 LDA QQ15 ; X = tmp_lo = s0_lo + s1_lo CLC ADC QQ15+2 TAX LDA QQ15+1 ; Y = tmp_hi = s1_hi + s1_hi + C ADC QQ15+3 TAY LDA QQ15+2 ; s0_lo = s1_lo STA QQ15 LDA QQ15+3 ; s0_hi = s1_hi STA QQ15+1 LDA QQ15+5 ; s1_hi = s2_hi STA QQ15+3 LDA QQ15+4 ; s1_lo = s2_lo STA QQ15+2 CLC ; s2_lo = X + s1_lo TXA ADC QQ15+2 STA QQ15+4 TYA ; s2_hi = Y + s1_hi + C ADC QQ15+3 STA QQ15+5 RTS ; The twist is complete so return from the subroutine ; ****************************************************************************** ; ; Name: TT146 ; Type: Subroutine ; Category: Universe ; Summary: Print the distance to the selected system in light years ; ; ------------------------------------------------------------------------------ ; ; If it is non-zero, print the distance to the selected system in light years. ; If it is zero, just move the text cursor down a line. ; ; Specifically, if the distance in QQ8 is non-zero, print token 31 ("DISTANCE"), ; then a colon, then the distance to one decimal place, then token 35 ("LIGHT ; YEARS"). If the distance is zero, move the cursor down one line. ; ; ****************************************************************************** .TT146 LDA QQ8 ; Take the two bytes of the 16-bit value in QQ8 and ORA QQ8+1 ; OR them together to check whether there are any BNE TT63 ; non-zero bits, and if so, jump to TT63 to print the ; distance JMP INCYC ; Move the text cursor down by one line and return from ; the subroutine using a tail call ;RTS ; This instruction is commented out in the original ; source .TT63 LDA #191 ; Print recursive token 31 ("DISTANCE") followed by JSR TT68 ; a colon LDX QQ8 ; Load (Y X) from QQ8, which contains the 16-bit LDY QQ8+1 ; distance we want to show SEC ; Set the C flag so that the call to pr5 will include a ; decimal point, and display the value as (Y X) / 10 JSR pr5 ; Print (Y X) to 5 digits, including a decimal point LDA #195 ; Set A to the recursive token 35 (" LIGHT YEARS") and ; fall through into TT60 to print the token followed ; by a paragraph break ; ****************************************************************************** ; ; Name: TT60 ; Type: Subroutine ; Category: Text ; Summary: Print a text token and a paragraph break ; ; ------------------------------------------------------------------------------ ; ; Print a text token (i.e. a character, control code, two-letter token or ; recursive token). Then print a paragraph break (a blank line between ; paragraphs) by moving the cursor down a line, setting Sentence Case, and then ; printing a newline. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text token to be printed ; ; ****************************************************************************** .TT60 JSR TT27 ; Print the text token in A and fall through into TTX69 ; to print the paragraph break ; ****************************************************************************** ; ; Name: TTX69 ; Type: Subroutine ; Category: Text ; Summary: Print a paragraph break ; ; ------------------------------------------------------------------------------ ; ; Print a paragraph break (a blank line between paragraphs) by moving the cursor ; down a line, setting Sentence Case, and then printing a newline. ; ; ****************************************************************************** .TTX69 JSR INCYC ; Move the text cursor down a line ;JSR INCYC ; This instruction is commented out in the original ; ; source ; Fall through into TT69 to set Sentence Case and print ; a newline ; ****************************************************************************** ; ; Name: TT69 ; Type: Subroutine ; Category: Text ; Summary: Set Sentence Case and print a newline ; ; ****************************************************************************** .TT69 LDA #%10000000 ; Set bit 7 of QQ17 to switch to Sentence Case STA QQ17 ; Fall through into TT67 to print a newline ; ****************************************************************************** ; ; Name: TT67 ; Type: Subroutine ; Category: Text ; Summary: Print a newline ; ; ****************************************************************************** .TT67 ;INC YC ; This instruction is commented out in the original ; source LDA #12 ; Load a newline character into A JMP TT27 ; Print the text token in A and return from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: TT70 ; Type: Subroutine ; Category: Universe ; Summary: Display "MAINLY " and jump to TT72 ; ; ------------------------------------------------------------------------------ ; ; This subroutine is called by TT25 when displaying a system's economy. ; ; ****************************************************************************** .TT70 LDA #173 ; Print recursive token 13 ("MAINLY ") JSR TT27 JMP TT72 ; Jump to TT72 to continue printing system data as part ; of routine TT25 ; ****************************************************************************** ; ; Name: spc ; Type: Subroutine ; Category: Text ; Summary: Print a text token followed by a space ; ; ------------------------------------------------------------------------------ ; ; Print a text token (i.e. a character, control code, two-letter token or ; recursive token) followed by a space. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text token to be printed ; ; ****************************************************************************** .spc JSR TT27 ; Print the text token in A JMP TT162 ; Print a space and return from the subroutine using a ; tail call ; ****************************************************************************** ; ; Name: TT25 ; Type: Subroutine ; Category: Universe ; Summary: Show the Data on System screen ; Deep dive: Generating system data ; Galaxy and system seeds ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; TT72 Used by TT70 to re-enter the routine after displaying ; "MAINLY" for the economy type ; ; ****************************************************************************** .TT25 LDA #1 ; Clear the screen, draw a border box, and set up a JSR TRADEMODE ; trading screen with a view type in QQ11 of 1 LDA #9 ; Move the text cursor to column 9 JSR DOXC LDA #163 ; Print recursive token 3 ("DATA ON {selected system JSR NLIN3 ; name}" and draw a horizontal line at pixel row 19 ; to box in the title JSR TTX69 ; Print a paragraph break and set Sentence Case JSR TT146 ; If the distance to this system is non-zero, print ; "DISTANCE", then the distance, "LIGHT YEARS" and a ; paragraph break, otherwise just move the cursor down ; a line LDA #194 ; Print recursive token 34 ("ECONOMY") followed by JSR TT68 ; a colon LDA QQ3 ; The system economy is determined by the value in QQ3, ; so fetch it into A. First we work out the system's ; prosperity as follows: ; ; QQ3 = 0 or 5 = %000 or %101 = Rich ; QQ3 = 1 or 6 = %001 or %110 = Average ; QQ3 = 2 or 7 = %010 or %111 = Poor ; QQ3 = 3 or 4 = %011 or %100 = Mainly CLC ; If (QQ3 + 1) >> 1 = %10, i.e. if QQ3 = %011 or %100 ADC #1 ; (3 or 4), then call TT70, which prints "MAINLY " and LSR A ; jumps down to TT72 to print the type of economy CMP #%00000010 BEQ TT70 LDA QQ3 ; If (QQ3 + 1) >> 1 < %10, i.e. if QQ3 = %000, %001 or BCC TT71 ; %010 (0, 1 or 2), then jump to TT71 with A set to the ; original value of QQ3 SBC #5 ; Here QQ3 = %101, %110 or %111 (5, 6 or 7), so subtract CLC ; 5 to bring it down to 0, 1 or 2 (the C flag is already ; set so the SBC will be correct) .TT71 ADC #170 ; A is now 0, 1 or 2, so print recursive token 10 + A. JSR TT27 ; This means that: ; ; QQ3 = 0 or 5 prints token 10 ("RICH ") ; QQ3 = 1 or 6 prints token 11 ("AVERAGE ") ; QQ3 = 2 or 7 prints token 12 ("POOR ") .TT72 LDA QQ3 ; Now to work out the type of economy, which is LSR A ; determined by bit 2 of QQ3, as follows: LSR A ; ; QQ3 bit 2 = 0 = Industrial ; QQ3 bit 2 = 1 = Agricultural ; ; So we fetch QQ3 into A and set A = bit 2 of QQ3 using ; two right shifts (which will work as QQ3 is only a ; 3-bit number) CLC ; Print recursive token 8 + A, followed by a paragraph ADC #168 ; break and Sentence Case, so: JSR TT60 ; ; QQ3 bit 2 = 0 prints token 8 ("INDUSTRIAL") ; QQ3 bit 2 = 1 prints token 9 ("AGRICULTURAL") LDA #162 ; Print recursive token 2 ("GOVERNMENT") followed by JSR TT68 ; a colon LDA QQ4 ; The system's government is determined by the value in ; QQ4, so fetch it into A CLC ; Print recursive token 17 + A, followed by a paragraph ADC #177 ; break and Sentence Case, so: JSR TT60 ; ; QQ4 = 0 prints token 17 ("ANARCHY") ; QQ4 = 1 prints token 18 ("FEUDAL") ; QQ4 = 2 prints token 19 ("MULTI-GOVERNMENT") ; QQ4 = 3 prints token 20 ("DICTATORSHIP") ; QQ4 = 4 prints token 21 ("COMMUNIST") ; QQ4 = 5 prints token 22 ("CONFEDERACY") ; QQ4 = 6 prints token 23 ("DEMOCRACY") ; QQ4 = 7 prints token 24 ("CORPORATE STATE") LDA #196 ; Print recursive token 36 ("TECH.LEVEL") followed by a JSR TT68 ; colon LDX QQ5 ; Fetch the tech level from QQ5 and increment it, as it INX ; is stored in the range 0-14 but the displayed range ; should be 1-15 CLC ; Call pr2 to print the technology level as a JSR pr2 ; three-digit number without a decimal point (by ; clearing the C flag) JSR TTX69 ; Print a paragraph break and set Sentence Case LDA #192 ; Print recursive token 32 ("POPULATION") followed by a JSR TT68 ; colon SEC ; Call pr2 to print the population as a three-digit LDX QQ6 ; number with a decimal point (by setting the C flag), JSR pr2 ; so the number printed will be population / 10 LDA #198 ; Print recursive token 38 (" BILLION"), followed by a JSR TT60 ; paragraph break and Sentence Case LDA #'(' ; Print an opening bracket JSR TT27 LDA QQ15+4 ; Now to calculate the species, so first check bit 7 of BMI TT75 ; s2_lo, and if it is set, jump to TT75 as this is an ; alien species LDA #188 ; Bit 7 of s2_lo is clear, so print recursive token 28 JSR TT27 ; ("HUMAN COLONIAL") JMP TT76 ; Jump to TT76 to print "S)" and a paragraph break, so ; the whole species string is "(HUMAN COLONIALS)" .TT75 LDA QQ15+5 ; This is an alien species, and we start with the first LSR A ; adjective, so fetch bits 2-7 of s2_hi into A and push LSR A ; onto the stack so we can use this later PHA AND #%00000111 ; Set A = bits 0-2 of A (so that's bits 2-4 of s2_hi) CMP #3 ; If A >= 3, jump to TT205 to skip the first adjective, BCS TT205 ADC #227 ; Otherwise A = 0, 1 or 2, so print recursive token JSR spc ; 67 + A, followed by a space, so: ; ; A = 0 prints token 67 ("LARGE") and a space ; A = 1 prints token 68 ("FIERCE") and a space ; A = 2 prints token 69 ("SMALL") and a space .TT205 PLA ; Now for the second adjective, so restore A to bits LSR A ; 2-7 of s2_hi, and throw away bits 2-4 to leave LSR A ; A = bits 5-7 of s2_hi LSR A CMP #6 ; If A >= 6, jump to TT206 to skip the second adjective BCS TT206 ADC #230 ; Otherwise A = 0 to 5, so print recursive token JSR spc ; 70 + A, followed by a space, so: ; ; A = 0 prints token 70 ("GREEN") and a space ; A = 1 prints token 71 ("RED") and a space ; A = 2 prints token 72 ("YELLOW") and a space ; A = 3 prints token 73 ("BLUE") and a space ; A = 4 prints token 74 ("BLACK") and a space ; A = 5 prints token 75 ("HARMLESS") and a space .TT206 LDA QQ15+3 ; Now for the third adjective, so EOR the high bytes of EOR QQ15+1 ; s0 and s1 and extract bits 0-2 of the result: AND #%00000111 ; STA QQ19 ; A = (s0_hi EOR s1_hi) AND %111 ; ; storing the result in QQ19 so we can use it later CMP #6 ; If A >= 6, jump to TT207 to skip the third adjective BCS TT207 ADC #236 ; Otherwise A = 0 to 5, so print recursive token JSR spc ; 76 + A, followed by a space, so: ; ; A = 0 prints token 76 ("SLIMY") and a space ; A = 1 prints token 77 ("BUG-EYED") and a space ; A = 2 prints token 78 ("HORNED") and a space ; A = 3 prints token 79 ("BONY") and a space ; A = 4 prints token 80 ("FAT") and a space ; A = 5 prints token 81 ("FURRY") and a space .TT207 LDA QQ15+5 ; Now for the actual species, so take bits 0-1 of AND #%00000011 ; s2_hi, add this to the value of A that we used for CLC ; the third adjective, and take bits 0-2 of the result ADC QQ19 AND #%00000111 ADC #242 ; A = 0 to 7, so print recursive token 82 + A, so: JSR TT27 ; ; A = 0 prints token 82 ("RODENT") ; A = 1 prints token 83 ("FROG") ; A = 2 prints token 84 ("LIZARD") ; A = 3 prints token 85 ("LOBSTER") ; A = 4 prints token 86 ("BIRD") ; A = 5 prints token 87 ("HUMANOID") ; A = 6 prints token 88 ("FELINE") ; A = 7 prints token 89 ("INSECT") .TT76 LDA #'S' ; Print an "S" to pluralise the species JSR TT27 LDA #')' ; And finally, print a closing bracket, followed by a JSR TT60 ; paragraph break and Sentence Case, to end the species ; section LDA #193 ; Print recursive token 33 ("GROSS PRODUCTIVITY"), JSR TT68 ; followed by a colon LDX QQ7 ; Fetch the 16-bit productivity value from QQ7 into LDY QQ7+1 ; (Y X) JSR pr6 ; Print (Y X) to 5 digits with no decimal point JSR TT162 ; Print a space LDA #0 ; Set QQ17 = 0 to switch to ALL CAPS STA QQ17 LDA #'M' ; Print "M" JSR TT27 LDA #226 ; Print recursive token 66 (" CR"), followed by a JSR TT60 ; paragraph break and Sentence Case LDA #250 ; Print recursive token 90 ("AVERAGE RADIUS"), followed JSR TT68 ; by a colon ; The average radius is calculated like this: ; ; ((s2_hi AND %1111) + 11) * 256 + s1_hi ; ; or, in terms of memory locations: ; ; ((QQ15+5 AND %1111) + 11) * 256 + QQ15+3 ; ; Because the multiplication is by 256, this is the ; same as saying a 16-bit number, with high byte: ; ; (QQ15+5 AND %1111) + 11 ; ; and low byte: ; ; QQ15+3 ; ; so we can set this up in (Y X) and call the pr5 ; routine to print it out LDA QQ15+5 ; Set A = QQ15+5 LDX QQ15+3 ; Set X = QQ15+3 AND #%00001111 ; Set Y = (A AND %1111) + 11 CLC ADC #11 TAY JSR pr5 ; Print (Y X) to 5 digits, not including a decimal ; point, as the C flag will be clear (as the maximum ; radius will always fit into 16 bits) JSR TT162 ; Print a space LDA #'k' ; Print "km" JSR TT26 LDA #'m' JSR TT26 JSR TTX69 ; Print a paragraph break and set Sentence Case ; By this point, ZZ contains the current system number ; which PDESC requires. It gets put there in the TT102 ; routine, which calls TT111 to populate ZZ before ; calling TT25 (this routine) JMP PDESC ; Jump to PDESC to print the system's extended ; description, returning from the subroutine using a ; tail call RTS ; Return from the subroutine (though this instruction ; has no effect as we already returned using a tail ; call) ; ****************************************************************************** ; ; Name: TT24 ; Type: Subroutine ; Category: Universe ; Summary: Calculate system data from the system seeds ; Deep dive: Generating system data ; Galaxy and system seeds ; ; ------------------------------------------------------------------------------ ; ; Calculate system data from the seeds in QQ15 and store them in the relevant ; locations. Specifically, this routine calculates the following from the three ; 16-bit seeds in QQ15 (using only s0_hi, s1_hi and s1_lo): ; ; QQ3 = economy (0-7) ; QQ4 = government (0-7) ; QQ5 = technology level (0-14) ; QQ6 = population * 10 (1-71) ; QQ7 = productivity (96-62480) ; ; The ranges of the various values are shown in brackets. Note that the radius ; and type of inhabitant are calculated on-the-fly in the TT25 routine when ; the system data gets displayed, so they aren't calculated here. ; ; ****************************************************************************** .TT24 LDA QQ15+1 ; Fetch s0_hi and extract bits 0-2 to determine the AND #%00000111 ; system's economy, and store in QQ3 STA QQ3 LDA QQ15+2 ; Fetch s1_lo and extract bits 3-5 to determine the LSR A ; system's government, and store in QQ4 LSR A LSR A AND #%00000111 STA QQ4 LSR A ; If government isn't anarchy or feudal, skip to TT77, BNE TT77 ; as we need to fix the economy of anarchy and feudal ; systems so they can't be rich LDA QQ3 ; Set bit 1 of the economy in QQ3 to fix the economy ORA #%00000010 ; for anarchy and feudal governments STA QQ3 .TT77 LDA QQ3 ; Now to work out the tech level, which we do like this: EOR #%00000111 ; CLC ; flipped_economy + (s1_hi AND %11) + (government / 2) STA QQ5 ; ; or, in terms of memory locations: ; ; QQ5 = (QQ3 EOR %111) + (QQ15+3 AND %11) + (QQ4 / 2) ; ; We start by setting QQ5 = QQ3 EOR %111 LDA QQ15+3 ; We then take the first 2 bits of s1_hi (QQ15+3) and AND #%00000011 ; add it into QQ5 ADC QQ5 STA QQ5 LDA QQ4 ; And finally we add QQ4 / 2 and store the result in LSR A ; QQ5, using LSR then ADC to divide by 2, which rounds ADC QQ5 ; up the result for odd-numbered government types STA QQ5 ASL A ; Now to work out the population, like so: ASL A ; ADC QQ3 ; (tech level * 4) + economy + government + 1 ADC QQ4 ; ADC #1 ; or, in terms of memory locations: STA QQ6 ; ; QQ6 = (QQ5 * 4) + QQ3 + QQ4 + 1 LDA QQ3 ; Finally, we work out productivity, like this: EOR #%00000111 ; ADC #3 ; (flipped_economy + 3) * (government + 4) STA P ; * population LDA QQ4 ; * 8 ADC #4 ; STA Q ; or, in terms of memory locations: JSR MULTU ; ; QQ7 = (QQ3 EOR %111 + 3) * (QQ4 + 4) * QQ6 * 8 ; ; We do the first step by setting P to the first ; expression in brackets and Q to the second, and ; calling MULTU, so now (A P) = P * Q. The highest this ; can be is 10 * 11 (as the maximum values of economy ; and government are 7), so the high byte of the result ; will always be 0, so we actually have: ; ; P = P * Q ; = (flipped_economy + 3) * (government + 4) LDA QQ6 ; We now take the result in P and multiply by the STA Q ; population to get the productivity, by setting Q to JSR MULTU ; the population from QQ6 and calling MULTU again, so ; now we have: ; ; (A P) = P * population ASL P ; Next we multiply the result by 8, as a 16-bit number, ROL A ; so we shift both bytes to the left three times, using ASL P ; the C flag to carry bits from bit 7 of the low byte ROL A ; into bit 0 of the high byte ASL P ROL A STA QQ7+1 ; Finally, we store the productivity in two bytes, with LDA P ; the low byte in QQ7 and the high byte in QQ7+1 STA QQ7 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TT22 ; Type: Subroutine ; Category: Charts ; Summary: Show the Long-range Chart ; Deep dive: A sense of scale ; ; ****************************************************************************** .TT22 LDA #64 ; Clear the screen, draw a border box, and set the JSR TT66 ; current view type in QQ11 to 32 (Long-range Chart) ;LDA #CYAN ; These instructions are commented out in the original ;JSR DOCOL ; source LDA #16 ; Switch to the palette for the trade view, though this JSR DOVDU19 ; doesn't actually do anything in this version of Elite LDA #7 ; Move the text cursor to column 7 JSR DOXC JSR TT81 ; Set the seeds in QQ15 to those of system 0 in the ; current galaxy (i.e. copy the seeds from QQ21 to QQ15) LDA #199 ; Print recursive token 39 ("GALACTIC CHART{galaxy JSR TT27 ; number right-aligned to width 3}") JSR NLIN ; Draw a horizontal line at pixel row 23 to box in the ; title and act as the top frame of the chart, and move ; the text cursor down one line LDA #152 ; Draw a screen-wide horizontal line at pixel row 152 JSR NLIN2 ; for the bottom edge of the chart, so the chart itself ; is 128 pixels high, starting on row 24 and ending on ; row 151 JSR TT14 ; Call TT14 to draw a circle with crosshairs at the ; current system's galactic coordinates LDX #0 ; We're now going to plot each of the galaxy's systems, ; so set up a counter in X for each system, starting at ; 0 and looping through to 255 .TT83 STX XSAV ; Store the counter in XSAV LDX QQ15+3 ; Fetch the s1_hi seed into X, which gives us the ; galactic x-coordinate of this system LDY QQ15+4 ; Fetch the s2_lo seed and set bits 4 and 6, storing the TYA ; result in ZZ to give a random number between 80 and ORA #%01010000 ; (but which will always be the same for this system). STA ZZ ; We use this value to determine the size of the point ; for this system on the chart by passing it as the ; distance argument to the PIXEL routine below LDA QQ15+1 ; Fetch the s0_hi seed into A, which gives us the ; galactic y-coordinate of this system LSR A ; We halve the y-coordinate because the galaxy in ; in Elite is rectangular rather than square, and is ; twice as wide (x-axis) as it is high (y-axis), so the ; chart is 256 pixels wide and 128 high CLC ; Add 24 to the halved y-coordinate and store in XX15+1 ADC #24 ; (as the top of the chart is on pixel row 24, just STA XX15+1 ; below the line we drew on row 23 above) JSR PIXEL ; Call PIXEL to draw a point at (X, A), with the size of ; the point dependent on the distance specified in ZZ ; (so a high value of ZZ will produce a one-pixel point, ; a medium value will produce a two-pixel dash, and a ; small value will produce a four-pixel square) JSR TT20 ; We want to move on to the next system, so call TT20 ; to twist the three 16-bit seeds in QQ15 LDX XSAV ; Restore the loop counter from XSAV INX ; Increment the counter BNE TT83 ; If X > 0 then we haven't done all 256 systems yet, so ; loop back up to TT83 ;JSR PBFL ; This instruction is commented out in the original ; ; source (it would call PBFL to send the contents of the ; ; pixel buffer to the I/O processor for plotting ; ; on-screen LDA QQ9 ; Set QQ19 to the selected system's x-coordinate STA QQ19 LDA QQ10 ; Set QQ19+1 to the selected system's y-coordinate, LSR A ; halved to fit it into the chart STA QQ19+1 LDA #4 ; Set QQ19+2 to size 4 for the crosshairs size STA QQ19+2 ; Fall through into TT15 to draw crosshairs of size 4 at ; the selected system's coordinates ; ****************************************************************************** ; ; Name: TT15 ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a set of crosshairs ; ; ------------------------------------------------------------------------------ ; ; For all views except the Short-range Chart, the centre is drawn 24 pixels to ; the right of the y-coordinate given. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; QQ19 The pixel x-coordinate of the centre of the crosshairs ; ; QQ19+1 The pixel y-coordinate of the centre of the crosshairs ; ; QQ19+2 The size of the crosshairs ; ; ****************************************************************************** .TT15 ;LDA #CYAN ; These instructions are commented out in the original ;JSR DOCOL ; source .TT15b LDA #24 ; Set A to 24, which we will use as the minimum ; screen indent for the crosshairs (i.e. the minimum ; distance from the top-left corner of the screen) LDX QQ11 ; If the current view is not the Short-range Chart, BPL TT178 ; which is the only view with bit 7 set, then jump to ; TT178 to skip the following instruction LDA #0 ; This is the Short-range Chart, so set A to 0, so the ; crosshairs can go right up against the screen edges .TT178 STA QQ19+5 ; Set QQ19+5 to A, which now contains the correct indent ; for this view LDA QQ19 ; Set A = crosshairs x-coordinate - crosshairs size SEC ; to get the x-coordinate of the left edge of the SBC QQ19+2 ; crosshairs BCS TT84 ; If the above subtraction didn't underflow, then A is ; positive, so skip the next instruction LDA #0 ; The subtraction underflowed, so set A to 0 so the ; crosshairs don't spill out of the left of the screen .TT84 ; In the following, the authors have used XX15 for ; temporary storage. XX15 shares location with X1, Y1, ; X2 and Y2, so in the following, you can consider ; the variables like this: ; ; XX15 is the same as X1 ; XX15+1 is the same as Y1 ; XX15+2 is the same as X2 ; XX15+3 is the same as Y2 ; ; Presumably this routine was written at a different ; time to the line-drawing routine, before the two ; workspaces were merged to save space STA XX15 ; Set XX15 (X1) = A (the x-coordinate of the left edge ; of the crosshairs) LDA QQ19 ; Set A = crosshairs x-coordinate + crosshairs size CLC ; to get the x-coordinate of the right edge of the ADC QQ19+2 ; crosshairs BCC TT85 ; If the above addition didn't overflow, then A is ; correct, so jump to TT85 to skip the next instruction LDA #255 ; The addition overflowed, so set A to 255 so the ; crosshairs don't spill out of the right of the screen ; (as 255 is the x-coordinate of the rightmost pixel ; on-screen) .TT85 STA XX15+2 ; Set XX15+2 (X2) = A (the x-coordinate of the right ; edge of the crosshairs) LDA QQ19+1 ; Set XX15+1 (Y1) = crosshairs y-coordinate + indent CLC ; to get the y-coordinate of the centre of the ADC QQ19+5 ; crosshairs STA XX15+1 STA XX15+3 ; Set XX15+3 (Y2) = crosshairs y-coordinate + indent JSR LL30 ; Draw a line from (X1, Y1) to (X2, Y2), where Y1 = Y2, ; which will draw from the left edge of the crosshairs ; to the right edge, through the centre of the ; crosshairs LDA QQ19+1 ; Set A = crosshairs y-coordinate - crosshairs size SEC ; to get the y-coordinate of the top edge of the SBC QQ19+2 ; crosshairs BCS TT86 ; If the above subtraction didn't underflow, then A is ; correct, so skip the next instruction LDA #0 ; The subtraction underflowed, so set A to 0 so the ; crosshairs don't spill out of the top of the screen .TT86 CLC ; Set XX15+1 (Y1) = A + indent to get the y-coordinate ADC QQ19+5 ; of the top edge of the indented crosshairs STA XX15+1 LDA QQ19+1 ; Set A = crosshairs y-coordinate + crosshairs size CLC ; + indent to get the y-coordinate of the bottom edge ADC QQ19+2 ; of the indented crosshairs ADC QQ19+5 CMP #152 ; If A < 152 then skip the following, as the crosshairs BCC TT87 ; won't spill out of the bottom of the screen LDX QQ11 ; A >= 152, so we need to check whether this will fit in ; this view, so fetch the view type BMI TT87 ; If this is the Short-range Chart then the y-coordinate ; is fine, so skip to TT87 LDA #151 ; Otherwise this is the Long-range Chart, so we need to ; clip the crosshairs at a maximum y-coordinate of 151 .TT87 STA XX15+3 ; Set XX15+3 (Y2) = A (the y-coordinate of the bottom ; edge of the crosshairs) LDA QQ19 ; Set XX15 (X1) = the x-coordinate of the centre of the STA XX15 ; crosshairs STA XX15+2 ; Set XX15+2 (X2) = the x-coordinate of the centre of ; the crosshairs JMP LL30 ; Draw a vertical line from (X1, Y1) to (X2, Y2), which ; will draw from the top edge of the crosshairs to the ; bottom edge, through the centre of the crosshairs, ; and returning from the subroutine using a tail call ; ****************************************************************************** ; ; Name: TT14 ; Type: Subroutine ; Category: Drawing circles ; Summary: Draw a circle with crosshairs on a chart ; ; ------------------------------------------------------------------------------ ; ; Draw a circle with crosshairs at the current system's galactic coordinates. ; ; ****************************************************************************** .TT126 LDA #104 ; Set QQ19 = 104, for the x-coordinate of the centre of STA QQ19 ; the fixed circle on the Short-range Chart LDA #90 ; Set QQ19+1 = 90, for the y-coordinate of the centre of STA QQ19+1 ; the fixed circle on the Short-range Chart LDA #16 ; Set QQ19+2 = 16, the size of the crosshairs on the STA QQ19+2 ; Short-range Chart JSR TT15 ; Draw the set of crosshairs defined in QQ19, at the ; exact coordinates as this is the Short-range Chart LDA QQ14 ; Set K to the fuel level from QQ14, so this can act as STA K ; the circle's radius (70 being a full tank) JMP TT128 ; Jump to TT128 to draw a circle with the centre at the ; same coordinates as the crosshairs, (QQ19, QQ19+1), ; and radius K that reflects the current fuel levels, ; returning from the subroutine using a tail call .TT14 ;LDA #CYAN ; These instructions are commented out in the original ;JSR DOCOL ; source LDA QQ11 ; If the current view is the Short-range Chart, which BMI TT126 ; is the only view with bit 7 set, then jump up to TT126 ; to draw the crosshairs and circle for that view ; Otherwise this is the Long-range Chart, so we draw the ; crosshairs and circle for that view instead LDA QQ14 ; Set K to the fuel level from QQ14 divided by 4, so LSR A ; this can act as the circle's radius (70 being a full LSR A ; tank, which divides down to a radius of 17) STA K LDA QQ0 ; Set QQ19 to the x-coordinate of the current system, STA QQ19 ; which will be the centre of the circle and crosshairs ; we draw LDA QQ1 ; Set QQ19+1 to the y-coordinate of the current system, LSR A ; halved because the galactic chart is half as high as STA QQ19+1 ; it is wide, which will again be the centre of the ; circle and crosshairs we draw LDA #7 ; Set QQ19+2 = 7, the size of the crosshairs on the STA QQ19+2 ; Long-range Chart JSR TT15 ; Draw the set of crosshairs defined in QQ19, which will ; be drawn 24 pixels to the right of QQ19+1 LDA QQ19+1 ; Add 24 to the y-coordinate of the crosshairs in QQ19+1 CLC ; so that the centre of the circle matches the centre ADC #24 ; of the crosshairs STA QQ19+1 ; Fall through into TT128 to draw a circle with the ; centre at the same coordinates as the crosshairs, ; (QQ19, QQ19+1), and radius K that reflects the ; current fuel levels ; ****************************************************************************** ; ; Name: TT128 ; Type: Subroutine ; Category: Drawing circles ; Summary: Draw a circle on a chart ; Deep dive: Drawing circles ; ; ------------------------------------------------------------------------------ ; ; Draw a circle with the centre at (QQ19, QQ19+1) and radius K. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; QQ19 The x-coordinate of the centre of the circle ; ; QQ19+1 The y-coordinate of the centre of the circle ; ; K The radius of the circle ; ; ****************************************************************************** .TT128 LDA QQ19 ; Set K3 = the x-coordinate of the centre STA K3 LDA QQ19+1 ; Set K4 = the y-coordinate of the centre STA K4 LDX #0 ; Set the high bytes of K3(1 0) and K4(1 0) to 0 STX K4+1 STX K3+1 ;STX LSX ; This instruction is commented out in the original ; source INX ; Set LSP = 1 to reset the ball line heap STX LSP LDX #2 ; Set STP = 2, the step size for the circle STX STP ;LDA #RED ; These instructions are commented out in the original ;JSR DOCOL ; source JMP CIRCLE2 ; Jump to CIRCLE2 to draw a circle with the centre at ; (K3(1 0), K4(1 0)) and radius K, returning from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: TT219 ; Type: Subroutine ; Category: Market ; Summary: Show the Buy Cargo screen ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; BAY2 Jump into the main loop at FRCE, setting the key ; "pressed" to the Inventory key ; ; ****************************************************************************** .TT219 LDA #2 ; Clear the screen, draw a border box, and set up a JSR TRADEMODE ; trading screen with a view type in QQ11 of 2 (Buy ; Cargo screen) JSR TT163 ; Print the column headers for the prices table LDA #%10000000 ; Set bit 7 of QQ17 to switch to Sentence Case, with the STA QQ17 ; next letter in capitals LDA #0 ; We're going to loop through all the available market STA QQ29 ; items, so we set up a counter in QQ29 to denote the ; current item and start it at 0 .TT220 JSR TT151 ; Call TT151 to print the item name, market price and ; availability of the current item, and set QQ24 to the ; item's price / 4, QQ25 to the quantity available and ; QQ19+1 to byte #1 from the market prices table for ; this item LDA QQ25 ; If there are some of the current item available, jump BNE TT224 ; to TT224 below to see if we want to buy any JMP TT222 ; Otherwise there are none available, so jump down to ; TT222 to skip this item .TQ4 LDY #176 ; Set Y to the recursive token 16 ("QUANTITY") .Tc JSR TT162 ; Print a space TYA ; Print the recursive token in Y followed by a question JSR prq ; mark .TTX224 JSR dn2 ; Call dn2 to make a short, high beep and delay for 1 ; second .TT224 JSR CLYNS ; Clear the bottom three text rows of the upper screen, ; and move the text cursor to the first cleared row LDA #204 ; Print recursive token 44 ("QUANTITY OF ") JSR TT27 LDA QQ29 ; Print recursive token 48 + QQ29, which will be in the CLC ; range 48 ("FOOD") to 64 ("ALIEN ITEMS"), so this ADC #208 ; prints the current item's name JSR TT27 LDA #'/' ; Print "/" JSR TT27 JSR TT152 ; Print the unit ("t", "kg" or "g") for the current item ; (as the call to TT151 above set QQ19+1 with the ; appropriate value) LDA #'?' ; Print "?" JSR TT27 JSR TT67 ; Print a newline LDX #0 ; These instructions have no effect, as they are STX R ; repeated at the start of gnum, which we call next. LDX #12 ; Perhaps they were left behind when code was moved from STX T1 ; here into gnum, and weren't deleted? .TT223K ; This label is a duplicate of a label in the gnum ; routine, so this could also be a remnant from code ; that got moved into the gnum subroutine ; ; In the original source this label is TT223, but ; because BeebAsm doesn't allow us to redefine labels, ; I have renamed it to TT223K JSR gnum ; Call gnum to get a number from the keyboard, which ; will be the quantity of this item we want to purchase, ; returning the number entered in A and R BCS TQ4 ; If gnum set the C flag, the number entered is greater ; than the quantity available, so jump up to TQ4 to ; display a "Quantity?" error, beep, clear the number ; and try again STA P ; Otherwise we have a valid purchase quantity entered, ; so store the amount we want to purchase in P JSR tnpr ; Call tnpr to work out whether there is room in the ; cargo hold for this item LDY #206 ; Set Y to recursive token 46 (" CARGO{sentence case}") ; to pass to the Tc routine if we call it LDA R ; If R = 0, then we didn't enter a number above, so skip BEQ P%+4 ; the following instruction BCS Tc ; If the C flag is set, then there is no room in the ; cargo hold, jump up to Tc to print a "Cargo?" error, ; beep, clear the number and try again LDA QQ24 ; There is room in the cargo hold, so now to check STA Q ; whether we have enough cash, so fetch the item's ; price / 4, which was returned in QQ24 by the call ; to TT151 above and store it in Q JSR GCASH ; Call GCASH to calculate: ; ; (Y X) = P * Q * 4 ; ; which will be the total price of this transaction ; (as P contains the purchase quantity and Q contains ; the item's price / 4) JSR LCASH ; Subtract (Y X) cash from the cash pot in CASH LDY #197 ; If the C flag is clear, we didn't have enough cash, BCC Tc ; so set Y to the recursive token 37 ("CASH") and jump ; up to Tc to print a "Cash?" error, beep, clear the ; number and try again LDY QQ29 ; Fetch the current market item number from QQ29 into Y LDA R ; Set A to the number of items we just purchased (this ; was set by gnum above) PHA ; Store the quantity just purchased on the stack CLC ; Add the number purchased to the Y-th byte of QQ20, ADC QQ20,Y ; which contains the number of items of this type in STA QQ20,Y ; our hold (so this transfers the bought items into our ; cargo hold) LDA AVL,Y ; Subtract the number of items from the Y-th byte of SEC ; AVL, which contains the number of items of this type SBC R ; that are available on the market STA AVL,Y PLA ; Restore the quantity just purchased BEQ TT222 ; If we didn't buy anything, jump to TT222 to skip the ; following instruction JSR dn ; Call dn to print the amount of cash left in the cash ; pot, then make a short, high beep to confirm the ; purchase, and delay for 1 second .TT222 LDA QQ29 ; Move the text cursor to row QQ29 + 5 (where QQ29 is CLC ; the item number, starting from 0) ADC #5 JSR DOYC LDA #0 ; Move the text cursor to column 0 JSR DOXC INC QQ29 ; Increment QQ29 to point to the next item LDA QQ29 ; If QQ29 >= 17 then jump to BAY2 as we have done the CMP #17 ; last item BCS BAY2 JMP TT220 ; Otherwise loop back to TT220 to print the next market ; item .BAY2 LDA #$10 ; Switch the text colour to white STA COL2 LDA #f9 ; Jump into the main loop at FRCE, setting the key JMP FRCE ; "pressed" to "9" (so we show the Inventory screen) ; ****************************************************************************** ; ; Name: gnum ; Type: Subroutine ; Category: Market ; Summary: Get a number from the keyboard ; ; ------------------------------------------------------------------------------ ; ; Get a number from the keyboard, up to the maximum number in QQ25, for the ; buying and selling of cargo and equipment. ; ; Pressing "Y" will return the maximum number (i.e. buy/sell all items), while ; pressing "N" will abort the sale and return a 0. ; ; Pressing a key with an ASCII code less than ASCII "0" will return a 0 in A (so ; that includes pressing Space or Return), while pressing a key with an ASCII ; code greater than ASCII "9" will jump to the Inventory screen (so that ; includes all letters and most punctuation). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; QQ25 The maximum number allowed ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A The number entered ; ; R Also contains the number entered ; ; C flag Set if the number is too large (> QQ25), clear otherwise ; ; ****************************************************************************** .gnum LDA #MAG2 ; Switch the text colour to purple STA COL2 LDX #0 ; We will build the number entered in R, so initialise STX R ; it with 0 LDX #12 ; We will check for up to 12 key presses, so set a STX T1 ; counter in T1 .TT223 JSR TT217 ; Scan the keyboard until a key is pressed, and return ; the key's ASCII code in A (and X) LDX R ; If R is non-zero then skip to NWDAV2, as we are BNE NWDAV2 ; already building a number CMP #'Y' ; If "Y" was pressed, jump to NWDAV1 to return the BEQ NWDAV1 ; maximum number allowed (i.e. buy/sell the whole stock) CMP #'N' ; If "N" was pressed, jump to NWDAV3 to return from the BEQ NWDAV3 ; subroutine with a result of 0 (i.e. abort transaction) .NWDAV2 STA Q ; Store the key pressed in Q SEC ; Subtract ASCII "0" from the key pressed, to leave the SBC #'0' ; numeric value of the key in A (if it was a number key) BCC OUT ; If A < 0, jump to OUT to load the current number and ; return from the subroutine, as the key pressed was ; RETURN (or some other character with a value less than ; ASCII "0") CMP #10 ; If A >= 10, jump to BAY2 to display the Inventory BCS BAY2 ; screen, as the key pressed was a letter or other ; non-digit and is greater than ASCII "9" STA S ; Store the numeric value of the key pressed in S LDA R ; Fetch the result so far into A CMP #26 ; If A >= 26, where A is the number entered so far, then BCS OUT ; adding a further digit will make it bigger than 256, ; so jump to OUT to return from the subroutine with the ; result in R (i.e. ignore the last key press) ASL A ; Set A = (A * 2) + (A * 8) = A * 10 STA T ASL A ASL A ADC T ADC S ; Add the pressed digit to A and store in R, so R now STA R ; contains its previous value with the new key press ; tacked onto the end CMP QQ25 ; If the result in R = the maximum allowed in QQ25, jump BEQ TT226 ; to TT226 to print the key press and keep looping (the ; BEQ is needed because the BCS below would jump to OUT ; if R >= QQ25, which we don't want) BCS OUT ; If the result in R > QQ25, jump to OUT to return from ; the subroutine with the result in R .TT226 LDA Q ; Print the character in Q (i.e. the key that was JSR TT26 ; pressed, as we stored the ASCII value in Q earlier) DEC T1 ; Decrement the loop counter BNE TT223 ; Loop back to TT223 until we have checked for 12 digits .OUT LDA #$10 ; Switch the text colour to white STA COL2 LDA R ; Set A to the result we have been building in R RTS ; Return from the subroutine .NWDAV1 ; If we get here then "Y" was pressed, so we return the ; maximum number allowed, which is in QQ25 JSR TT26 ; Print the character for the key that was pressed LDA QQ25 ; Set R = QQ25, so we return the maximum value allowed STA R JMP OUT ; Jump to OUT to return from the subroutine .NWDAV3 ; If we get here then "N" was pressed, so we return 0 JSR TT26 ; Print the character for the key that was pressed LDA #0 ; Set R = 0, so we return 0 STA R JMP OUT ; Jump to OUT to return from the subroutine ; ****************************************************************************** ; ; Name: NWDAV4 ; Type: Subroutine ; Category: Market ; Summary: Print an "ITEM?" error, make a beep and rejoin the TT210 routine ; ; ****************************************************************************** .NWDAV4 JSR TT67 ; Print a newline LDA #176 ; Print recursive token 127 ("ITEM") followed by a JSR prq ; question mark JSR dn2 ; Call dn2 to make a short, high beep and delay for 1 ; second LDY QQ29 ; Fetch the item number we are selling from QQ29 JMP NWDAVxx ; Jump back into the TT210 routine that called NWDAV4 ; ****************************************************************************** ; ; Name: TT208 ; Type: Subroutine ; Category: Market ; Summary: Show the Sell Cargo screen ; ; ****************************************************************************** .TT208 LDA #4 ; Clear the screen, draw a border box, and set up a JSR TRADEMODE ; trading screen with a view type in QQ11 of 4 (Sell ; Cargo screen) LDA #10 ; Move the text cursor to column 10 JSR DOXC LDA #205 ; Print recursive token 45 ("SELL") JSR TT27 LDA #206 ; Print recursive token 46 (" CARGO{sentence case}") JSR NLIN3 ; draw a horizontal line at pixel row 19 to box in the ; title JSR TT67 ; Print a newline ; Fall through into TT210 to show the Inventory screen ; with the option to sell ; ****************************************************************************** ; ; Name: TT210 ; Type: Subroutine ; Category: Market ; Summary: Show a list of current cargo in our hold, optionally to sell ; ; ------------------------------------------------------------------------------ ; ; Show a list of current cargo in our hold, either with the ability to sell (the ; Sell Cargo screen) or without (the Inventory screen), depending on the current ; view. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; QQ11 The current view: ; ; * 4 = Sell Cargo ; ; * 8 = Inventory ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; NWDAVxx Used to rejoin this routine from the call to NWDAV4 ; ; ****************************************************************************** .TT210 LDY #0 ; We're going to loop through all the available market ; items and check whether we have any in the hold (and, ; if we are in the Sell Cargo screen, whether we want ; to sell any items), so we set up a counter in Y to ; denote the current item and start it at 0 .TT211 STY QQ29 ; Store the current item number in QQ29 .NWDAVxx LDX QQ20,Y ; Fetch into X the amount of the current item that we BEQ TT212 ; have in our cargo hold, which is stored in QQ20+Y, ; and if there are no items of this type in the hold, ; jump down to TT212 to skip to the next item TYA ; Set Y = Y * 4, so this will act as an index into the ASL A ; market prices table at QQ23 for this item (as there ASL A ; are four bytes per item in the table) TAY LDA QQ23+1,Y ; Fetch byte #1 from the market prices table for the STA QQ19+1 ; current item and store it in QQ19+1, for use by the ; call to TT152 below TXA ; Store the amount of item in the hold (in X) on the PHA ; stack JSR TT69 ; Call TT69 to set Sentence Case and print a newline CLC ; Print recursive token 48 + QQ29, which will be in the LDA QQ29 ; range 48 ("FOOD") to 64 ("ALIEN ITEMS"), so this ADC #208 ; prints the current item's name JSR TT27 LDA #14 ; Move the text cursor to column 14, for the item's JSR DOXC ; quantity PLA ; Restore the amount of item in the hold into X TAX STA QQ25 ; Store the amount of this item in the hold in QQ25 CLC ; Print the 8-bit number in X to 3 digits, without a JSR pr2 ; decimal point JSR TT152 ; Print the unit ("t", "kg" or "g") for the market item ; whose byte #1 from the market prices table is in ; QQ19+1 (which we set up above) LDA QQ11 ; If the current view type in QQ11 is not 4 (Sell Cargo CMP #4 ; screen), jump to TT212 to skip the option to sell BNE TT212 ; items ;JSR TT162 ; This instruction is commented out in the original ; source LDA #205 ; Print recursive token 45 ("SELL") JSR TT27 LDA #206 ; Print extended token 206 ("{all caps}(Y/N)?") JSR DETOK JSR gnum ; Call gnum to get a number from the keyboard, which ; will be the number of the item we want to sell, ; returning the number entered in A and R, and setting ; the C flag if the number is bigger than the available ; amount of this item in QQ25 BEQ TT212 ; If no number was entered, jump to TT212 to move on to ; the next item BCS NWDAV4 ; If the number entered was too big, jump to NWDAV4 to ; print an "ITEM?" error, make a beep and rejoin the ; routine at NWDAVxx above LDA QQ29 ; We are selling this item, so fetch the item number ; from QQ29 LDX #255 ; Set QQ17 = 255 to disable printing STX QQ17 JSR TT151 ; Call TT151 to set QQ24 to the item's price / 4 (the ; routine doesn't print the item details, as we just ; disabled printing) LDY QQ29 ; Subtract R (the number of items we just asked to buy) LDA QQ20,Y ; from the available amount of this item in QQ20, as we SEC ; just bought them SBC R STA QQ20,Y LDA R ; Set P to the amount of this item we just bought STA P LDA QQ24 ; Set Q to the item's price / 4 STA Q JSR GCASH ; Call GCASH to calculate ; ; (Y X) = P * Q * 4 ; ; which will be the total price we make from this sale ; (as P contains the quantity we're selling and Q ; contains the item's price / 4) JSR MCASH ; Add (Y X) cash to the cash pot in CASH LDA #0 ; We've made the sale, so set the amount STA QQ17 ; Set QQ17 = 0, which enables printing again .TT212 LDY QQ29 ; Fetch the item number from QQ29 into Y, and increment INY ; Y to point to the next item CPY #17 ; Loop back to TT211 to print the next item in the hold BCC TT211 ; until Y = 17 (at which point we have done the last ; item) LDA QQ11 ; If the current view type in QQ11 is not 4 (Sell Cargo CMP #4 ; screen), skip the next two instructions and move on to BNE P%+8 ; printing the number of Trumbles JSR dn2 ; This is the Sell Cargo screen, so call dn2 to make a ; short, high beep and delay for 1 second JMP BAY2 ; And then jump to BAY2 to display the Inventory ; screen, as we have finished selling cargo JSR TT69 ; Call TT69 to set Sentence Case and print a newline LDA TRIBBLE ; If there are any Trumbles in the hold, skip the ORA TRIBBLE+1 ; following RTS and continue on (in the Master version, BNE P%+3 ; there are never any Trumbles, so this value will ; always be zero) .zebra RTS ; There are no Trumbles in the hold, so return from the ; subroutine ; If we get here then we have Trumbles in the hold, so ; we print out the number (though we never get here in ; the Master version) CLC ; Clear the C flag, so the call to TT11 below doesn't ; include a decimal point LDA #0 ; Set A = 0, for the call to TT11 below, so we don't pad ; out the number of Trumbles LDX TRIBBLE ; Fetch the number of Trumbles into (Y X) LDY TRIBBLE+1 JSR TT11 ; Call TT11 to print the number of Trumbles in (Y X), ; with no decimal point JSR DORND ; Print out a random extended token from 111 to 114, all AND #3 ; of which are blank in this version of Elite CLC ADC #111 JSR DETOK LDA #198 ; Print extended token 198, which is blank, but would JSR DETOK ; contain the text "LITTLE TRUMBLE" if the Trumbles ; mission was enabled LDA TRIBBLE+1 ; If we have more than 256 Trumbles, skip to DOANS BNE DOANS LDX TRIBBLE ; If we have exactly one Trumble, return from the DEX ; subroutine (as zebra contains an RTS) BEQ zebra .DOANS LDA #'s' ; We have more than one Trumble, so print an 's' and JMP DASC ; return from the subroutine using a tail call ; ****************************************************************************** ; ; Name: TT213 ; Type: Subroutine ; Category: Market ; Summary: Show the Inventory screen ; ; ****************************************************************************** .TT213 LDA #8 ; Clear the screen, draw a border box, and set up a JSR TRADEMODE ; trading screen with a view type in QQ11 of 4 ; (Inventory screen) LDA #11 ; Move the text cursor to column 11 to print the screen JSR DOXC ; title LDA #164 ; Print recursive token 4 ("INVENTORY{crlf}") followed JSR TT60 ; by a paragraph break and Sentence Case JSR NLIN4 ; Draw a horizontal line at pixel row 19 to box in the ; title. The authors could have used a call to NLIN3 ; instead and saved the above call to TT60, but you ; just can't optimise everything JSR fwl ; Call fwl to print the fuel and cash levels on two ; separate lines LDA CRGO ; If our ship's cargo capacity is < 26 (i.e. we do not CMP #26 ; have a cargo bay extension), skip the following two BCC P%+7 ; instructions LDA #107 ; We do have a cargo bay extension, so print recursive JSR TT27 ; token 107 ("LARGE CARGO{sentence case} BAY") JMP TT210 ; Jump to TT210 to print the contents of our cargo bay ; and return from the subroutine using a tail call ; ****************************************************************************** ; ; Name: TT214 ; Type: Subroutine ; Category: Keyboard ; Summary: Ask a question with a "Y/N?" prompt and return the response ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text token to print before the "Y/N?" prompt ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if the response was "yes", clear otherwise ; ; ****************************************************************************** .TT214 ;.TT214 ; These instructions are commented out in the original ; ; source ;PHA ;JSR TT162 ;PLA .TT221 JSR TT27 ; Print the text token in A LDA #206 ; Print extended token 206 ("{all caps}(Y/N)?") JSR DETOK JSR TT217 ; Scan the keyboard until a key is pressed, and return ; the key's ASCII code in A and X ORA #%00100000 ; Set bit 5 in the value of the key pressed, which ; converts it to lower case CMP #'y' ; If "y" was pressed, jump to TT218 BEQ TT218 LDA #'n' ; Otherwise jump to TT26 to print "n" and return from JMP TT26 ; the subroutine using a tail call (so all other ; responses apart from "y" indicate a no) .TT218 JSR TT26 ; Print the character in A, i.e. print "y" SEC ; Set the C flag to indicate a "yes" response RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TT16 ; Type: Subroutine ; Category: Charts ; Summary: Move the crosshairs on a chart ; ; ------------------------------------------------------------------------------ ; ; Move the chart crosshairs by the amount in X and Y. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The amount to move the crosshairs in the x-axis ; ; Y The amount to move the crosshairs in the y-axis ; ; ****************************************************************************** .TT16 TXA ; Push the change in X onto the stack (let's call this PHA ; the x-delta) DEY ; Negate the change in Y and push it onto the stack TYA ; (let's call this the y-delta) EOR #$FF PHA JSR WSCAN ; Call WSCAN to wait for the vertical sync, so the whole ; screen gets drawn and we can move the crosshairs with ; no screen flicker JSR TT103 ; Draw small crosshairs at coordinates (QQ9, QQ10), ; which will erase the crosshairs currently there PLA ; Store the y-delta in QQ19+3 and fetch the current STA QQ19+3 ; y-coordinate of the crosshairs from QQ10 into A, ready LDA QQ10 ; for the call to TT123 JSR TT123 ; Call TT123 to move the selected system's galactic ; y-coordinate by the y-delta, putting the new value in ; QQ19+4 LDA QQ19+4 ; Store the updated y-coordinate in QQ10 (the current STA QQ10 ; y-coordinate of the crosshairs) STA QQ19+1 ; This instruction has no effect, as QQ19+1 is ; overwritten below, both in TT103 and TT105 PLA ; Store the x-delta in QQ19+3 and fetch the current STA QQ19+3 ; x-coordinate of the crosshairs from QQ10 into A, ready LDA QQ9 ; for the call to TT123 JSR TT123 ; Call TT123 to move the selected system's galactic ; x-coordinate by the x-delta, putting the new value in ; QQ19+4 LDA QQ19+4 ; Store the updated x-coordinate in QQ9 (the current STA QQ9 ; x-coordinate of the crosshairs) STA QQ19 ; This instruction has no effect, as QQ19 is overwritten ; below, both in TT103 and TT105 ; Now we've updated the coordinates of the crosshairs, ; fall through into TT103 to redraw them at their new ; location ; ****************************************************************************** ; ; Name: TT103 ; Type: Subroutine ; Category: Charts ; Summary: Draw a small set of crosshairs on a chart ; ; ------------------------------------------------------------------------------ ; ; Draw a small set of crosshairs on a galactic chart at the coordinates in ; (QQ9, QQ10). ; ; ****************************************************************************** .TT103 LDA QQ11 ; Fetch the current view type into A BMI TT105 ; If this is the Short-range Chart screen, jump to TT105 LDA QQ9 ; Store the crosshairs x-coordinate in QQ19 STA QQ19 LDA QQ10 ; Halve the crosshairs y-coordinate and store it in QQ19 LSR A ; (we halve it because the Long-range Chart is half as STA QQ19+1 ; high as it is wide) LDA #4 ; Set QQ19+2 to 4 denote crosshairs of size 4 STA QQ19+2 JMP TT15 ; Jump to TT15 to draw crosshairs of size 4 at the ; crosshairs coordinates, returning from the subroutine ; using a tail call ; ****************************************************************************** ; ; Name: TT123 ; Type: Subroutine ; Category: Charts ; Summary: Move galactic coordinates by a signed delta ; ; ------------------------------------------------------------------------------ ; ; Move an 8-bit galactic coordinate by a certain distance in either direction ; (i.e. a signed 8-bit delta), but only if it doesn't cause the coordinate to ; overflow. The coordinate is in a single axis, so it's either an x-coordinate ; or a y-coordinate. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The galactic coordinate to update ; ; QQ19+3 The delta (can be positive or negative) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; QQ19+4 The updated coordinate after moving by the delta (this ; will be the same as A if moving by the delta overflows) ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; TT180 Contains an RTS ; ; ****************************************************************************** .TT123 STA QQ19+4 ; Store the original coordinate in temporary storage at ; QQ19+4 CLC ; Set A = A + QQ19+3, so A now contains the original ADC QQ19+3 ; coordinate, moved by the delta LDX QQ19+3 ; If the delta is negative, jump to TT124 BMI TT124 BCC TT125 ; If the C flag is clear, then the above addition didn't ; overflow, so jump to TT125 to return the updated value RTS ; Otherwise the C flag is set and the above addition ; overflowed, so do not update the return value .TT124 BCC TT180 ; If the C flag is clear, then because the delta is ; negative, this indicates the addition (which is ; effectively a subtraction) underflowed, so jump to ; TT180 to return from the subroutine without updating ; the return value .TT125 STA QQ19+4 ; Store the updated coordinate in QQ19+4 .TT180 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TT105 ; Type: Subroutine ; Category: Charts ; Summary: Draw crosshairs on the Short-range Chart, with clipping ; ; ------------------------------------------------------------------------------ ; ; Check whether the crosshairs are close enough to the current system to appear ; on the Short-range Chart, and if so, draw them. ; ; ****************************************************************************** .TT105 LDA QQ9 ; Set A = QQ9 - QQ0, the horizontal distance between the SEC ; crosshairs (QQ9) and the current system (QQ0) SBC QQ0 CMP #38 ; If the horizontal distance in A < 38, then the BCC TT179 ; crosshairs are close enough to the current system to ; appear in the Short-range Chart, so jump to TT179 to ; check the vertical distance CMP #230 ; If the horizontal distance in A < -26, then the BCC TT180 ; crosshairs are too far from the current system to ; appear in the Short-range Chart, so jump to TT180 to ; return from the subroutine (as TT180 contains an RTS) .TT179 ASL A ; Set QQ19 = 104 + A * 4 ASL A ; CLC ; 104 is the x-coordinate of the centre of the chart, ADC #104 ; so this sets QQ19 to the screen pixel x-coordinate STA QQ19 ; of the crosshairs LDA QQ10 ; Set A = QQ10 - QQ1, the vertical distance between the SEC ; crosshairs (QQ10) and the current system (QQ1) SBC QQ1 CMP #38 ; If the vertical distance in A is < 38, then the BCC P%+6 ; crosshairs are close enough to the current system to ; appear in the Short-range Chart, so skip the next two ; instructions CMP #220 ; If the horizontal distance in A is < -36, then the BCC TT180 ; crosshairs are too far from the current system to ; appear in the Short-range Chart, so jump to TT180 to ; return from the subroutine (as TT180 contains an RTS) ASL A ; Set QQ19+1 = 90 + A * 2 CLC ; ADC #90 ; 90 is the y-coordinate of the centre of the chart, STA QQ19+1 ; so this sets QQ19+1 to the screen pixel x-coordinate ; of the crosshairs LDA #8 ; Set QQ19+2 to 8 denote crosshairs of size 8 STA QQ19+2 JMP TT15 ; Jump to TT15 to draw crosshairs of size 8 at the ; crosshairs coordinates, returning from the subroutine ; using a tail call ; ****************************************************************************** ; ; Name: TT23 ; Type: Subroutine ; Category: Charts ; Summary: Show the Short-range Chart ; ; ****************************************************************************** .TT23 LDA #199 ; Set the number of pixel rows in the space view to 199, STA Yx2M1 ; to cover the size of the chart part of the Short-range ; Chart view STA dontclip ; Set dontclip to 199 (which has bit 7 set) to disable ; line-clipping in the LL145 routine ; ; This allows the Short-range Chart to take up the whole ; of the screen, rather than being clipped to the ; dimensions of the space view (which we don't want to ; do as there is no dashboard in the chart view) LDA #128 ; Clear the top part of the screen, draw a border box, JSR TT66 ; and set the current view type in QQ11 to 128 (Short- ; range Chart) LDA #16 ; Switch to the palette for the trade view, though this JSR DOVDU19 ; doesn't actually do anything in this version of Elite ;LDA #CYAN ; These instructions are commented out in the original ;JSR DOCOL ; source LDA #7 ; Move the text cursor to column 7 JSR DOXC LDA #190 ; Print recursive token 30 ("SHORT RANGE CHART") and JSR NLIN3 ; draw a horizontal line at pixel row 19 to box in the ; title JSR TT14 ; Call TT14 to draw a circle with crosshairs at the ; current system's galactic coordinates JSR TT103 ; Draw small crosshairs at coordinates (QQ9, QQ10), ; i.e. at the selected system JSR TT81 ; Set the seeds in QQ15 to those of system 0 in the ; current galaxy (i.e. copy the seeds from QQ21 to QQ15) ;LDA #CYAN ; These instructions are commented out in the original ;JSR DOCOL ; source LDA #0 ; Set A = 0, which we'll use below to zero out the INWK ; workspace STA XX20 ; We're about to start working our way through each of ; the galaxy's systems, so set up a counter in XX20 for ; each system, starting at 0 and looping through to 255 LDX #24 ; First, though, we need to zero out the 25 bytes at ; INWK so we can use them to work out which systems have ; room for a label, so set a counter in X for 25 bytes .EE3 STA INWK,X ; Set the X-th byte of INWK to zero DEX ; Decrement the counter BPL EE3 ; Loop back to EE3 for the next byte until we've zeroed ; all 25 bytes ; We now loop through every single system in the galaxy ; and check the distance from the current system whose ; coordinates are in (QQ0, QQ1). We get the galactic ; coordinates of each system from the system's seeds, ; like this: ; ; x = s1_hi (which is stored in QQ15+3) ; y = s0_hi (which is stored in QQ15+1) ; ; so the following loops through each system in the ; galaxy in turn and calculates the distance between ; (QQ0, QQ1) and (s1_hi, s0_hi) to find the closest one .TT182 LDA QQ15+3 ; Set A = s1_hi - QQ0, the horizontal distance between SEC ; (s1_hi, s0_hi) and (QQ0, QQ1) SBC QQ0 BCS TT184 ; If a borrow didn't occur, i.e. s1_hi >= QQ0, then the ; result is positive, so jump to TT184 and skip the ; following two instructions EOR #$FF ; Otherwise negate the result in A, so A is always ADC #1 ; positive (i.e. A = |s1_hi - QQ0|) .TT184 CMP #20 ; If the horizontal distance in A is >= 20, then this BCS TT187 ; system is too far away from the current system to ; appear in the Short-range Chart, so jump to TT187 to ; move on to the next system LDA QQ15+1 ; Set A = s0_hi - QQ1, the vertical distance between SEC ; (s1_hi, s0_hi) and (QQ0, QQ1) SBC QQ1 BCS TT186 ; If a borrow didn't occur, i.e. s0_hi >= QQ1, then the ; result is positive, so jump to TT186 and skip the ; following two instructions EOR #$FF ; Otherwise negate the result in A, so A is always ADC #1 ; positive (i.e. A = |s0_hi - QQ1|) .TT186 CMP #38 ; If the vertical distance in A is >= 38, then this BCS TT187 ; system is too far away from the current system to ; appear in the Short-range Chart, so jump to TT187 to ; move on to the next system ; This system should be shown on the Short-range Chart, ; so now we need to work out where the label should go, ; and set up the various variables we need to draw the ; system's filled circle on the chart LDA QQ15+3 ; Set A = s1_hi - QQ0, the horizontal distance between SEC ; this system and the current system, where |A| < 20. SBC QQ0 ; Let's call this the x-delta, as it's the horizontal ; difference between the current system at the centre of ; the chart, and this system (and this time we keep the ; sign of A, so it can be negative if it's to the left ; of the chart's centre, or positive if it's to the ; right) ASL A ; Set XX12 = 104 + x-delta * 4 ASL A ; ADC #104 ; 104 is the x-coordinate of the centre of the chart, STA XX12 ; so this sets XX12 to the centre 104 +/- 76, the pixel ; x-coordinate of this system LSR A ; Move the text cursor to column x-delta / 2 + 1 LSR A ; which will be in the range 1-10 LSR A CLC ADC #1 JSR DOXC LDA QQ15+1 ; Set A = s0_hi - QQ1, the vertical distance between SEC ; this system and the current system, where |A| < 38. SBC QQ1 ; Let's call this the y-delta, as it's the vertical ; difference between the current system at the centre of ; the chart, and this system (and this time we keep the ; sign of A, so it can be negative if it's above the ; chart's centre, or positive if it's below) ASL A ; Set K4 = 90 + y-delta * 2 ADC #90 ; STA K4 ; 90 is the y-coordinate of the centre of the chart, ; so this sets K4 to the centre 90 +/- 74, the pixel ; y-coordinate of this system LSR A ; Set Y = A >> 3 LSR A ; = K4 div 8 LSR A ; TAY ; So Y now contains the number of the character row ; that contains this system ; Now to see if there is room for this system's label. ; Ideally we would print the system name on the same ; text row as the system, but we only want to print one ; label per row, to prevent overlap, so now we check ; this system's row, and if that's already occupied, ; the row above, and if that's already occupied, the ; row below... and if that's already occupied, we give ; up and don't print a label for this system LDX INWK,Y ; If the value in INWK+Y is 0 (i.e. the text row BEQ EE4 ; containing this system does not already have another ; system's label on it), jump to EE4 to store this ; system's label on this row INY ; If the value in INWK+Y+1 is 0 (i.e. the text row below LDX INWK,Y ; the one containing this system does not already have BEQ EE4 ; another system's label on it), jump to EE4 to store ; this system's label on this row DEY ; If the value in INWK+Y-1 is 0 (i.e. the text row above DEY ; the one containing this system does not already have LDX INWK,Y ; another system's label on it), fall through into to BNE ee1 ; EE4 to store this system's label on this row, ; otherwise jump to ee1 to skip printing a label for ; this system (as there simply isn't room) .EE4 TYA ; Now to print the label, so move the text cursor to row JSR DOYC ; Y (which contains the row where we can print this ; system's label) CPY #3 ; If Y < 3, then the system would clash with the chart BCC TT187 ; title, so jump to TT187 to skip showing the system LDA #$FF ; Store $FF in INWK+Y, to denote that this row is now STA INWK,Y ; occupied so we don't try to print another system's ; label on this row LDA #%10000000 ; Set bit 7 of QQ17 to switch to Sentence Case STA QQ17 JSR cpl ; Call cpl to print out the system name for the seeds ; in QQ15 (which now contains the seeds for the current ; system) .ee1 LDA #0 ; Now to plot the star, so set the high bytes of K, K3 STA K3+1 ; and K4 to 0 STA K4+1 STA K+1 LDA XX12 ; Set the low byte of K3 to XX12, the pixel x-coordinate STA K3 ; of this system LDA QQ15+5 ; Fetch s2_hi for this system from QQ15+5, extract bit 0 AND #1 ; and add 2 to get the size of the star, which we store ADC #2 ; in K. This will be either 2, 3 or 4, depending on the STA K ; value of bit 0, and whether the C flag is set (which ; will vary depending on what happens in the above call ; to cpl). Incidentally, the planet's average radius ; also uses s2_hi, bits 0-3 to be precise, but that ; doesn't mean the two sizes affect each other ; We now have the following: ; ; K(1 0) = radius of star (2, 3 or 4) ; ; K3(1 0) = pixel x-coordinate of system ; ; K4(1 0) = pixel y-coordinate of system ; ; which we can now pass to the SUN routine to draw a ; small "sun" on the Short-range Chart for this system JSR FLFLLS ; Call FLFLLS to reset the LSO block JSR SUN ; Call SUN to plot a sun with radius K at pixel ; coordinate (K3, K4) JSR FLFLLS ; Call FLFLLS to reset the LSO block .TT187 JSR TT20 ; We want to move on to the next system, so call TT20 ; to twist the three 16-bit seeds in QQ15 INC XX20 ; Increment the counter BEQ P%+5 ; If X = 0 then we have done all 256 systems, so skip ; the next instruction to return from the subroutine JMP TT182 ; Otherwise jump back up to TT182 to process the next ; system LDA #0 ; Set dontclip to 0 to enable line-clipping in the LL145 STA dontclip ; routine, as we only disable this for the Short-range ; Chart LDA #2*Y-1 ; Set Yx2M1 to the number of pixel lines in the space STA Yx2M1 ; view RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TT81 ; Type: Subroutine ; Category: Universe ; Summary: Set the selected system's seeds to those of system 0 ; ; ------------------------------------------------------------------------------ ; ; Copy the three 16-bit seeds for the current galaxy's system 0 (QQ21) into the ; seeds for the selected system (QQ15) - in other words, set the selected ; system's seeds to those of system 0. ; ; ****************************************************************************** .TT81 LDX #5 ; Set up a counter in X to copy six bytes (for three ; 16-bit numbers) LDA QQ21,X ; Copy the X-th byte in QQ21 to the X-th byte in QQ15 STA QQ15,X DEX ; Decrement the counter BPL TT81+2 ; Loop back up to the LDA instruction if we still have ; more bytes to copy RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TT111 ; Type: Subroutine ; Category: Universe ; Summary: Set the current system to the nearest system to a point ; ; ------------------------------------------------------------------------------ ; ; Given a set of galactic coordinates in (QQ9, QQ10), find the nearest system ; to this point in the galaxy, and set this as the currently selected system. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; QQ9 The x-coordinate near which we want to find a system ; ; QQ10 The y-coordinate near which we want to find a system ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; QQ8(1 0) The distance from the current system to the nearest ; system to the original coordinates ; ; QQ9 The x-coordinate of the nearest system to the original ; coordinates ; ; QQ10 The y-coordinate of the nearest system to the original ; coordinates ; ; QQ15 to QQ15+5 The three 16-bit seeds of the nearest system to the ; original coordinates ; ; ZZ The system number of the nearest system ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; TT111-1 Contains an RTS ; ; ****************************************************************************** .TT111 JSR TT81 ; Set the seeds in QQ15 to those of system 0 in the ; current galaxy (i.e. copy the seeds from QQ21 to QQ15) ; We now loop through every single system in the galaxy ; and check the distance from (QQ9, QQ10). We get the ; galactic coordinates of each system from the system's ; seeds, like this: ; ; x = s1_hi (which is stored in QQ15+3) ; y = s0_hi (which is stored in QQ15+1) ; ; so the following loops through each system in the ; galaxy in turn and calculates the distance between ; (QQ9, QQ10) and (s1_hi, s0_hi) to find the closest one LDY #127 ; Set Y = T = 127 to hold the shortest distance we've STY T ; found so far, which we initially set to half the ; distance across the galaxy, or 127, as our coordinate ; system ranges from (0,0) to (255, 255) LDA #0 ; Set A = U = 0 to act as a counter for each system in STA U ; the current galaxy, which we start at system 0 and ; loop through to 255, the last system .TT130 LDA QQ15+3 ; Set A = s1_hi - QQ9, the horizontal distance between SEC ; (s1_hi, s0_hi) and (QQ9, QQ10) SBC QQ9 BCS TT132 ; If a borrow didn't occur, i.e. s1_hi >= QQ9, then the ; result is positive, so jump to TT132 and skip the ; following two instructions EOR #$FF ; Otherwise negate the result in A, so A is always ADC #1 ; positive (i.e. A = |s1_hi - QQ9|) .TT132 LSR A ; Set S = A / 2 STA S ; = |s1_hi - QQ9| / 2 LDA QQ15+1 ; Set A = s0_hi - QQ10, the vertical distance between SEC ; (s1_hi, s0_hi) and (QQ9, QQ10) SBC QQ10 BCS TT134 ; If a borrow didn't occur, i.e. s0_hi >= QQ10, then the ; result is positive, so jump to TT134 and skip the ; following two instructions EOR #$FF ; Otherwise negate the result in A, so A is always ADC #1 ; positive (i.e. A = |s0_hi - QQ10|) .TT134 LSR A ; Set A = S + A / 2 CLC ; = |s1_hi - QQ9| / 2 + |s0_hi - QQ10| / 2 ADC S ; ; So A now contains the sum of the horizontal and ; vertical distances, both divided by 2 so the result ; fits into one byte, and although this doesn't contain ; the actual distance between the systems, it's a good ; enough approximation to use for comparing distances CMP T ; If A >= T, then this system's distance is bigger than BCS TT135 ; our "minimum distance so far" stored in T, so it's no ; closer than the systems we have already found, so ; skip to TT135 to move on to the next system STA T ; This system is the closest to (QQ9, QQ10) so far, so ; update T with the new "distance" approximation LDX #5 ; As this system is the closest we have found yet, we ; want to store the system's seeds in case it ends up ; being the closest of all, so we set up a counter in X ; to copy six bytes (for three 16-bit numbers) .TT136 LDA QQ15,X ; Copy the X-th byte in QQ15 to the X-th byte in QQ19, STA QQ19,X ; where QQ15 contains the seeds for the system we just ; found to be the closest so far, and QQ19 is temporary ; storage DEX ; Decrement the counter BPL TT136 ; Loop back to TT136 if we still have more bytes to ; copy LDA U ; Store the system number U in ZZ, so when we are done STA ZZ ; looping through all the candidates, the winner's ; number will be in ZZ .TT135 JSR TT20 ; We want to move on to the next system, so call TT20 ; to twist the three 16-bit seeds in QQ15 INC U ; Increment the system counter in U BNE TT130 ; If U > 0 then we haven't done all 256 systems yet, so ; loop back up to TT130 ; We have now finished checking all the systems in the ; galaxy, and the seeds for the closest system are in ; QQ19, so now we want to copy these seeds to QQ15, ; to set the selected system to this closest system LDX #5 ; So we set up a counter in X to copy six bytes (for ; three 16-bit numbers) .TT137 LDA QQ19,X ; Copy the X-th byte in QQ19 to the X-th byte in QQ15 STA QQ15,X DEX ; Decrement the counter BPL TT137 ; Loop back to TT137 if we still have more bytes to ; copy LDA QQ15+1 ; The y-coordinate of the system described by the seeds STA QQ10 ; in QQ15 is in QQ15+1 (s0_hi), so we copy this to QQ10 ; as this is where we store the selected system's ; y-coordinate LDA QQ15+3 ; The x-coordinate of the system described by the seeds STA QQ9 ; in QQ15 is in QQ15+3 (s1_hi), so we copy this to QQ9 ; as this is where we store the selected system's ; x-coordinate ; We have now found the closest system to (QQ9, QQ10) ; and have set it as the selected system, so now we ; need to work out the distance between the selected ; system and the current system SEC ; Set A = QQ9 - QQ0, the horizontal distance between SBC QQ0 ; the selected system's x-coordinate (QQ9) and the ; current system's x-coordinate (QQ0) BCS TT139 ; If a borrow didn't occur, i.e. QQ9 >= QQ0, then the ; result is positive, so jump to TT139 and skip the ; following two instructions EOR #$FF ; Otherwise negate the result in A, so A is always ADC #1 ; positive (i.e. A = |QQ9 - QQ0|) ; A now contains the difference between the two ; systems' x-coordinates, with the sign removed. We ; will refer to this as the x-delta ("delta" means ; change or difference in maths) .TT139 JSR SQUA2 ; Set (A P) = A * A ; = |QQ9 - QQ0| ^ 2 ; = x_delta ^ 2 STA K+1 ; Store (A P) in K(1 0) LDA P STA K LDA QQ10 ; Set A = QQ10 - QQ1, the vertical distance between the SEC ; selected system's y-coordinate (QQ10) and the current SBC QQ1 ; system's y-coordinate (QQ1) BCS TT141 ; If a borrow didn't occur, i.e. QQ10 >= QQ1, then the ; result is positive, so jump to TT141 and skip the ; following two instructions EOR #$FF ; Otherwise negate the result in A, so A is always ADC #1 ; positive (i.e. A = |QQ10 - QQ1|) .TT141 LSR A ; Set A = A / 2 ; A now contains the difference between the two ; systems' y-coordinates, with the sign removed, and ; halved. We halve the value because the galaxy in ; in Elite is rectangular rather than square, and is ; twice as wide (x-axis) as it is high (y-axis), so to ; get a distance that matches the shape of the ; long-range galaxy chart, we need to halve the ; distance between the vertical y-coordinates. We will ; refer to this as the y-delta JSR SQUA2 ; Set (A P) = A * A ; = (|QQ10 - QQ1| / 2) ^ 2 ; = y_delta ^ 2 ; By this point we have the following results: ; ; K(1 0) = x_delta ^ 2 ; (A P) = y_delta ^ 2 ; ; so to find the distance between the two points, we ; can use Pythagoras - so first we need to add the two ; results together, and then take the square root PHA ; Store the high byte of the y-axis value on the stack, ; so we can use A for another purpose LDA P ; Set Q = P + K, which adds the low bytes of the two CLC ; calculated values ADC K STA Q PLA ; Restore the high byte of the y-axis value from the ; stack into A again ADC K+1 ; Set A = A + K+1, which adds the high bytes of the two ; calculated values BCC P%+4 ; If the above addition overflowed, set A = 255 LDA #255 STA R ; Store A in R, so we now have R = A + K+1, and: ; ; (R Q) = K(1 0) + (A P) ; = (x_delta ^ 2) + (y_delta ^ 2) JSR LL5 ; Set Q = SQRT(R Q), so Q now contains the distance ; between the two systems, in terms of coordinates ; We now store the distance to the selected system * 4 ; in the two-byte location QQ8, by taking (0 Q) and ; shifting it left twice, storing it in QQ8(1 0) LDA Q ; First we shift the low byte left by setting ASL A ; A = Q * 2, with bit 7 of A going into the C flag LDX #0 ; Now we set the high byte in QQ8+1 to 0 and rotate STX QQ8+1 ; the C flag into bit 0 of QQ8+1 ROL QQ8+1 ASL A ; And then we repeat the shift left of (QQ8+1 A) ROL QQ8+1 STA QQ8 ; And store A in the low byte, QQ8, so QQ8(1 0) now ; contains Q * 4. Given that the width of the galaxy is ; 256 in coordinate terms, the width of the galaxy ; would be 1024 in the units we store in QQ8 JMP TT24 ; Call TT24 to calculate system data from the seeds in ; QQ15 and store them in the relevant locations, so our ; new selected system is fully set up, and return from ; the subroutine using a tail call ; ****************************************************************************** ; ; Name: dockEd ; Type: Subroutine ; Category: Flight ; Summary: Print a message to say there is no hyperspacing allowed inside the ; station ; ; ------------------------------------------------------------------------------ ; ; Print "Docked" at the bottom of the screen to indicate we can't hyperspace ; when docked. ; ; ****************************************************************************** .dockEd JSR CLYNS ; Clear the bottom three text rows of the upper screen, ; and move the text cursor to the first cleared row LDA #15 ; Move the text cursor to column 15 JSR DOXC ;LDA #RED ; These instructions are commented out in the original ;JSR DOCOL ; source LDA #205 ; Print extended token 205 ("DOCKED") and return from JMP DETOK ; the subroutine using a tail call ; ****************************************************************************** ; ; Name: hyp ; Type: Subroutine ; Category: Flight ; Summary: Start the hyperspace process ; ; ------------------------------------------------------------------------------ ; ; Called when "H" or CTRL-H is pressed during flight. Checks the following: ; ; * We are in space ; ; * We are not already in a hyperspace countdown ; ; If CTRL is being held down, we jump to Ghy to engage the galactic hyperdrive, ; otherwise we check that: ; ; * The selected system is not the current system ; ; * We have enough fuel to make the jump ; ; and if all the pre-jump checks are passed, we print the destination on-screen ; and start the countdown. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; TTX111 Used to rejoin this routine from the call to TTX110 ; ; ****************************************************************************** .hyp LDA QQ12 ; If we are docked (QQ12 = $FF) then jump to dockEd to BNE dockEd ; print an error message and return from the subroutine ; using a tail call (as we can't hyperspace when docked) LDA QQ22+1 ; Fetch QQ22+1, which contains the number that's shown ; on-screen during hyperspace countdown BEQ P%+3 ; If it is zero, skip the next instruction RTS ; The count is non-zero, so return from the subroutine ;LDA #CYAN ; These instructions are commented out in the original ;JSR DOCOL ; source JSR CTRL ; Scan the keyboard to see if CTRL is currently pressed BMI Ghy ; If it is, then the galactic hyperdrive has been ; activated, so jump to Ghy to process it LDA QQ11 ; If the current view is 0 (i.e. the space view) then BEQ TTX110 ; jump to TTX110, which calls TT111 to set the current ; system to the nearest system to (QQ9, QQ10), and jumps ; back into this routine at TTX111 below AND #%11000000 ; If either bit 6 or 7 of the view type is set - so BNE P%+3 ; this is either the Short-range or Long-range Chart - ; then skip the following instruction RTS ; This is not a chart view, so return from the ; subroutine JSR hm ; This is a chart view, so call hm to redraw the chart ; crosshairs .TTX111 ; If we get here then the current view is either the ; space view or a chart LDA QQ8 ; If either byte of the distance to the selected system ORA QQ8+1 ; in QQ8 are zero, skip the next instruction to make a BNE P%+3 ; copy of the destination seeds in safehouse RTS ; The selected system is the same as the current system, ; so return from the subroutine LDX #5 ; We now want to copy those seeds into safehouse, so we ; so set a counter in X to copy 6 bytes .sob LDA QQ15,X ; Copy the X-th byte of QQ15 into the X-th byte of STA safehouse,X ; safehouse DEX ; Decrement the loop counter BPL sob ; Loop back to copy the next byte until we have copied ; all six seed bytes LDA #7 ; Move the text cursor to column 7 JSR DOXC LDA #23 ; Set A = 23 to use as the text row for views other ; than the space view LDY QQ11 ; If QQ11 = 0 then this is the space view, so set A = 17 BNE P%+4 ; to use as the text row LDA #17 JSR DOYC ; Move the text cursor to row 17 (in the space view) or ; 23 (otherwise), which is in the middle of the bottom ; text row) LDA #0 ; Set QQ17 = 0 to switch to ALL CAPS STA QQ17 LDA #189 ; Print recursive token 29 ("HYPERSPACE ") JSR TT27 LDA QQ8+1 ; If the high byte of the distance to the selected BNE goTT147 ; system in QQ8 is > 0, then it is definitely too far to ; jump (as our maximum range is 7.0 light years, or a ; value of 70 in QQ8(1 0)), so jump to TT147 via goTT147 ; to print "RANGE?" and return from the subroutine using ; a tail call LDA QQ14 ; Fetch our current fuel level from Q114 into A CMP QQ8 ; If our fuel reserves are greater than or equal to the BCS P%+5 ; distance to the selected system, then we have enough ; fuel for this jump, so skip the following instruction ; to start the hyperspace countdown .goTT147 JMP TT147 ; We don't have enough fuel to reach the destination, so ; jump to TT147 to print "RANGE?" and return from the ; subroutine using a tail call LDA #'-' ; Print a hyphen JSR TT27 JSR cpl ; Call cpl to print the name of the selected system ; Fall through into wW to start the hyperspace countdown ; ****************************************************************************** ; ; Name: wW ; Type: Subroutine ; Category: Flight ; Summary: Start a hyperspace countdown ; ; ------------------------------------------------------------------------------ ; ; Start the hyperspace countdown (for both inter-system hyperspace and the ; galactic hyperdrive). ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; wW2 Start the hyperspace countdown, starting the countdown ; from the value in A ; ; ****************************************************************************** .wW LDA #15 ; The hyperspace countdown starts from 15, so set A to ; 15 so we can set the two hyperspace counters .wW2 STA QQ22+1 ; Set the number in QQ22+1 to A, which is the number ; that's shown on-screen during the hyperspace countdown STA QQ22 ; Set the number in QQ22 to 15, which is the internal ; counter that counts down by 1 each iteration of the ; main game loop, and each time it reaches zero, the ; on-screen counter gets decremented, and QQ22 gets set ; to 5, so setting QQ22 to 15 here makes the first tick ; of the hyperspace counter longer than subsequent ticks TAX ; Print the 8-bit number in X (i.e. 15) at text location JMP ee3 ; (0, 1), padded to 5 digits, so it appears in the top ; left corner of the screen, and return from the ; subroutine using a tail call ;.hy5 ; This instruction and the hy5 label are commented out ; ; in the original - they can actually be found at the ;RTS ; end of the jmp routine below, so perhaps this is where ; they were originally, but the authors realised they ; could save a byte by using a tail call instead of an ; RTS? ; ****************************************************************************** ; ; Name: TTX110 ; Type: Subroutine ; Category: Flight ; Summary: Set the current system to the nearest system and return to hyp ; ; ****************************************************************************** .TTX110 ; This routine is only called from the hyp routine, and ; it jumps back into hyp at label TTX111 JSR TT111 ; Call TT111 to set the current system to the nearest ; system to (QQ9, QQ10), and put the seeds of the ; nearest system into QQ15 to QQ15+5 JMP TTX111 ; Return to TTX111 in the hyp routine ; ****************************************************************************** ; ; Name: Ghy ; Type: Subroutine ; Category: Flight ; Summary: Perform a galactic hyperspace jump ; Deep dive: Twisting the system seeds ; Galaxy and system seeds ; ; ------------------------------------------------------------------------------ ; ; Engage the galactic hyperdrive. Called from the hyp routine above if CTRL-H is ; being pressed. ; ; This routine also updates the galaxy seeds to point to the next galaxy. Using ; a galactic hyperdrive rotates each seed byte to the left, rolling each byte ; left within itself like this: ; ; 01234567 -> 12345670 ; ; to get the seeds for the next galaxy. So after 8 galactic jumps, the seeds ; roll round to those of the first galaxy again. ; ; We always arrive in a new galaxy at galactic coordinates (96, 96), and then ; find the nearest system and set that as our location. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; zZ+1 Contains an RTS ; ; ****************************************************************************** .Ghy LDX GHYP ; Fetch GHYP, which tells us whether we own a galactic BEQ zZ+1 ; hyperdrive, and if it is zero, which means we don't, ; return from the subroutine (as zZ+1 contains an RTS) INX ; We own a galactic hyperdrive, so X is $FF, so this ; instruction sets X = 0 ;STX QQ8 ; These instructions are commented out in the original ;STX QQ8+1 ; source STX GHYP ; The galactic hyperdrive is a one-use item, so set GHYP ; to 0 so we no longer have one fitted STX FIST ; Changing galaxy also clears our criminal record, so ; set our legal status in FIST to 0 ("clean") LDA #2 ; Call wW2 with A = 2 to start the hyperspace countdown, JSR wW2 ; but starting the countdown from 2 LDX #5 ; To move galaxy, we rotate the galaxy's seeds left, so ; set a counter in X for the 6 seed bytes INC GCNT ; Increment the current galaxy number in GCNT LDA GCNT ; Clear bit 3 of GCNT, so we jump from galaxy 7 back AND #%11110111 ; to galaxy 0 (shown in-game as going from galaxy 8 back STA GCNT ; to the starting point in galaxy 1). We also retain any ; set bits in the high nibble, so if the galaxy number ; is manually set to 16 or higher, it will stay high ; (though the high nibble doesn't seem to get set by ; the game at any point, so it isn't clear what this is ; for, though Lave in galaxy 16 does show a unique ; system description override, so something is going on ; here...) .G1 LDA QQ21,X ; Load the X-th seed byte into A ASL A ; Set the C flag to bit 7 of the seed ROL QQ21,X ; Rotate the seed in memory, which will add bit 7 back ; in as bit 0, so this rolls the seed around on itself DEX ; Decrement the counter BPL G1 ; Loop back for the next seed byte, until we have ; rotated them all ;JSR DORND ; This instruction is commented out in the original ; source, and would set A and X to random numbers, so ; perhaps the original plan was to arrive in each new ; galaxy in a random place? .zZ LDA #96 ; Set (QQ9, QQ10) to (96, 96), which is where we always STA QQ9 ; arrive in a new galaxy (the selected system will be STA QQ10 ; set to the nearest actual system later on) JSR TT110 ; Call TT110 to show the front space view JSR TT111 ; Call TT111 to set the current system to the nearest ; system to (QQ9, QQ10), and put the seeds of the ; nearest system into QQ15 to QQ15+5 ; ; This call fixes a bug in the cassette version, where ; the galactic hyperdrive will take us to coordinates ; (96, 96) in the new galaxy, even if there isn't ; actually a system there, so if we jump when we are ; low on fuel, it is possible to get stuck in the ; middle of nowhere when changing galaxy ; ; This call sets the current system correctly, so we ; always arrive at the nearest system to (96, 96) LDX #5 ; We now want to copy those seeds into safehouse, so we ; so set a counter in X to copy 6 bytes .dumdeedum LDA QQ15,X ; Copy the X-th byte of QQ15 into the X-th byte of STA safehouse,X ; safehouse DEX ; Decrement the loop counter BPL dumdeedum ; Loop back to copy the next byte until we have copied ; all six seed bytes LDX #0 ; Set the distance to the selected system in QQ8(1 0) STX QQ8 ; to 0 STX QQ8+1 LDA #116 ; Print recursive token 116 (GALACTIC HYPERSPACE ") JSR MESS ; as an in-flight message ; Fall through into jmp to set the system to the ; current system and return from the subroutine there ; ****************************************************************************** ; ; Name: jmp ; Type: Subroutine ; Category: Universe ; Summary: Set the current system to the selected system ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; (QQ0, QQ1) The galactic coordinates of the new system ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; hy5 Contains an RTS ; ; ****************************************************************************** .jmp LDA QQ9 ; Set the current system's galactic x-coordinate to the STA QQ0 ; x-coordinate of the selected system LDA QQ10 ; Set the current system's galactic y-coordinate to the STA QQ1 ; y-coordinate of the selected system .hy5 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: ee3 ; Type: Subroutine ; Category: Flight ; Summary: Print the hyperspace countdown in the top-left of the screen ; ; ------------------------------------------------------------------------------ ; ; 5 digits, left-padding with spaces for numbers with fewer than 3 digits (so ; numbers < 10000 are right-aligned), with no decimal point. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The number to print ; ; ****************************************************************************** .ee3 ;LDA #RED ; These instructions are commented out in the original ;JSR DOCOL ; source LDA #1 ; Move the text cursor to column 1 on row 1 JSR DOXC JSR DOYC LDY #0 ; Set Y = 0 for the high byte in pr6 CLC ; Call TT11 to print X to 3 digits with no decimal point LDA #3 ; and return from the subroutine using a tail call JMP TT11 ; ****************************************************************************** ; ; Name: pr6 ; Type: Subroutine ; Category: Text ; Summary: Print 16-bit number, left-padded to 5 digits, no point ; ; ------------------------------------------------------------------------------ ; ; Print the 16-bit number in (Y X) to 5 digits, left-padding with spaces for ; numbers with fewer than 3 digits (so numbers < 10000 are right-aligned), ; with no decimal point. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The low byte of the number to print ; ; Y The high byte of the number to print ; ; ****************************************************************************** .pr6 CLC ; Do not display a decimal point when printing ; Fall through into pr5 to print X to 5 digits ; ****************************************************************************** ; ; Name: pr5 ; Type: Subroutine ; Category: Text ; Summary: Print a 16-bit number, left-padded to 5 digits, and optional point ; ; ------------------------------------------------------------------------------ ; ; Print the 16-bit number in (Y X) to 5 digits, left-padding with spaces for ; numbers with fewer than 3 digits (so numbers < 10000 are right-aligned). ; Optionally include a decimal point. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The low byte of the number to print ; ; Y The high byte of the number to print ; ; C flag If set, include a decimal point ; ; ****************************************************************************** .pr5 LDA #5 ; Set the number of digits to print to 5 JMP TT11 ; Call TT11 to print (Y X) to 5 digits and return from ; the subroutine using a tail call ; ****************************************************************************** ; ; Name: TT147 ; Type: Subroutine ; Category: Flight ; Summary: Print an error when a system is out of hyperspace range ; ; ------------------------------------------------------------------------------ ; ; Print "RANGE?" for when the hyperspace distance is too far ; ; ****************************************************************************** .TT147 LDA #202 ; Load A with token 42 ("RANGE") and fall through into ; prq to print it, followed by a question mark ; ****************************************************************************** ; ; Name: prq ; Type: Subroutine ; Category: Text ; Summary: Print a text token followed by a question mark ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text token to be printed ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; prq+3 Print a question mark ; ; ****************************************************************************** .prq JSR TT27 ; Print the text token in A LDA #'?' ; Print a question mark and return from the JMP TT27 ; subroutine using a tail call ; ****************************************************************************** ; ; Name: TT151 ; Type: Subroutine ; Category: Market ; Summary: Print the name, price and availability of a market item ; Deep dive: Market item prices and availability ; Galaxy and system seeds ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The number of the market item to print, 0-16 (see QQ23 ; for details of item numbers) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; QQ19+1 Byte #1 from the market prices table for this item ; ; QQ24 The item's price / 4 ; ; QQ25 The item's availability ; ; ****************************************************************************** .TT151q ; We jump here from below if we are in witchspace PLA ; Restore the item number from the stack RTS ; Return from the subroutine .TT151 PHA ; Store the item number on the stack and in QQ19+4 STA QQ19+4 ASL A ; Store the item number * 4 in QQ19, so this will act as ASL A ; an index into the market prices table at QQ23 for this STA QQ19 ; item (as there are four bytes per item in the table) LDA MJ ; If we are in witchspace, we can't trade items, so jump BNE TT151q ; up to TT151q to return from the subroutine LDA #1 ; Move the text cursor to column 1, for the item's name JSR DOXC PLA ; Restore the item number ADC #208 ; Print recursive token 48 + A, which will be in the JSR TT27 ; range 48 ("FOOD") to 64 ("ALIEN ITEMS"), so this ; prints the item's name LDA #14 ; Move the text cursor to column 14, for the price JSR DOXC LDX QQ19 ; Fetch byte #1 from the market prices table (units and LDA QQ23+1,X ; economic_factor) for this item and store in QQ19+1 STA QQ19+1 LDA QQ26 ; Fetch the random number for this system visit and AND QQ23+3,X ; AND with byte #3 from the market prices table (mask) ; to give: ; ; A = random AND mask CLC ; Add byte #0 from the market prices table (base_price), ADC QQ23,X ; so we now have: STA QQ24 ; ; A = base_price + (random AND mask) JSR TT152 ; Call TT152 to print the item's unit ("t", "kg" or ; "g"), padded to a width of two characters JSR var ; Call var to set QQ19+3 = economy * |economic_factor| ; (and set the availability of alien items to 0) LDA QQ19+1 ; Fetch the byte #1 that we stored above and jump to BMI TT155 ; TT155 if it is negative (i.e. if the economic_factor ; is negative) LDA QQ24 ; Set A = QQ24 + QQ19+3 ADC QQ19+3 ; ; = base_price + (random AND mask) ; + (economy * |economic_factor|) ; ; which is the result we want, as the economic_factor ; is positive JMP TT156 ; Jump to TT156 to multiply the result by 4 .TT155 LDA QQ24 ; Set A = QQ24 - QQ19+3 SEC ; SBC QQ19+3 ; = base_price + (random AND mask) ; - (economy * |economic_factor|) ; ; which is the result we want, as economic_factor ; is negative .TT156 STA QQ24 ; Store the result in QQ24 and P STA P LDA #0 ; Set A = 0 and call GC2 to calculate (Y X) = (A P) * 4, JSR GC2 ; which is the same as (Y X) = P * 4 because A = 0 SEC ; We now have our final price, * 10, so we can call pr5 JSR pr5 ; to print (Y X) to 5 digits, including a decimal ; point, as the C flag is set LDY QQ19+4 ; We now move on to availability, so fetch the market ; item number that we stored in QQ19+4 at the start LDA #5 ; Set A to 5 so we can print the availability to 5 ; digits (right-padded with spaces) LDX AVL,Y ; Set X to the item's availability, which is given in ; the AVL table STX QQ25 ; Store the availability in QQ25 CLC ; Clear the C flag BEQ TT172 ; If none are available, jump to TT172 to print a tab ; and a "-" JSR pr2+2 ; Otherwise print the 8-bit number in X to 5 digits, ; right-aligned with spaces. This works because we set ; A to 5 above, and we jump into the pr2 routine just ; after the first instruction, which would normally ; set the number of digits to 3 JMP TT152 ; Print the unit ("t", "kg" or "g") for the market item, ; with a following space if required to make it two ; characters long, and return from the subroutine using ; a tail call .TT172 LDA #25 ; Move the text cursor to column 25 JSR DOXC LDA #'-' ; Print a "-" character by jumping to TT162+2, which BNE TT162+2 ; contains JMP TT27 (this BNE is effectively a JMP as A ; will never be zero), and return from the subroutine ; using a tail call ; ****************************************************************************** ; ; Name: TT152 ; Type: Subroutine ; Category: Market ; Summary: Print the unit ("t", "kg" or "g") for a market item ; ; ------------------------------------------------------------------------------ ; ; Print the unit ("t", "kg" or "g") for the market item whose byte #1 from the ; market prices table is in QQ19+1, right-padded with spaces to a width of two ; characters (so that's "t ", "kg" or "g "). ; ; ****************************************************************************** .TT152 LDA QQ19+1 ; Fetch the economic_factor from QQ19+1 AND #96 ; If bits 5 and 6 are both clear, jump to TT160 to BEQ TT160 ; print "t" for tonne, followed by a space, and return ; from the subroutine using a tail call CMP #32 ; If bit 5 is set, jump to TT161 to print "kg" for BEQ TT161 ; kilograms, and return from the subroutine using a tail ; call JSR TT16a ; Otherwise call TT16a to print "g" for grams, and fall ; through into TT162 to print a space and return from ; the subroutine ; ****************************************************************************** ; ; Name: TT162 ; Type: Subroutine ; Category: Text ; Summary: Print a space ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; TT162+2 Jump to TT27 to print the text token in A ; ; ****************************************************************************** .TT162 LDA #' ' ; Load a space character into A JMP TT27 ; Print the text token in A and return from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: TT160 ; Type: Subroutine ; Category: Market ; Summary: Print "t" (for tonne) and a space ; ; ****************************************************************************** .TT160 LDA #'t' ; Load a "t" character into A JSR TT26 ; Print the character, using TT216 so that it doesn't ; change the character case BCC TT162 ; Jump to TT162 to print a space and return from the ; subroutine using a tail call (this BCC is effectively ; a JMP as the C flag is cleared by TT26) ; ****************************************************************************** ; ; Name: TT161 ; Type: Subroutine ; Category: Market ; Summary: Print "kg" (for kilograms) ; ; ****************************************************************************** .TT161 LDA #'k' ; Load a "k" character into A JSR TT26 ; Print the character, using TT216 so that it doesn't ; change the character case, and fall through into ; TT16a to print a "g" character ; ****************************************************************************** ; ; Name: TT16a ; Type: Subroutine ; Category: Market ; Summary: Print "g" (for grams) ; ; ****************************************************************************** .TT16a LDA #'g' ; Load a "g" character into A JMP TT26 ; Print the character, using TT216 so that it doesn't ; change the character case, and return from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: TT163 ; Type: Subroutine ; Category: Market ; Summary: Print the headers for the table of market prices ; ; ------------------------------------------------------------------------------ ; ; Print the column headers for the prices table in the Buy Cargo and Market ; Price screens. ; ; ****************************************************************************** .TT163 LDA #17 ; Move the text cursor in XC to column 17 JSR DOXC LDA #255 ; Print recursive token 95 token ("UNIT QUANTITY BNE TT162+2 ; {crlf} PRODUCT UNIT PRICE FOR SALE{crlf}{lf}") by ; jumping to TT162+2, which contains JMP TT27 (this BNE ; is effectively a JMP as A will never be zero), and ; return from the subroutine using a tail call ; ****************************************************************************** ; ; Name: TT167 ; Type: Subroutine ; Category: Market ; Summary: Show the Market Price screen ; ; ****************************************************************************** .TT167 LDA #16 ; Clear the screen, draw a border box, and set up a JSR TRADEMODE ; trading screen with a view type in QQ11 of 32 (Market ; Price screen) LDA #5 ; Move the text cursor to column 5 JSR DOXC LDA #167 ; Print recursive token 7 ("{current system name} MARKET JSR NLIN3 ; PRICES") and draw a horizontal line at pixel row 19 ; to box in the title LDA #3 ; Move the text cursor to row 3 JSR DOYC JSR TT163 ; Print the column headers for the prices table LDA #6 ; Move the text cursor to row 6 JSR DOYC LDA #0 ; We're going to loop through all the available market STA QQ29 ; items, so we set up a counter in QQ29 to denote the ; current item and start it at 0 .TT168 LDX #%10000000 ; Set bit 7 of QQ17 to switch to Sentence Case, with the STX QQ17 ; next letter in capitals JSR TT151 ; Call TT151 to print the item name, market price and ; availability of the current item, and set QQ24 to the ; item's price / 4, QQ25 to the quantity available and ; QQ19+1 to byte #1 from the market prices table for ; this item JSR INCYC ; Move the text cursor down one row INC QQ29 ; Increment QQ29 to point to the next item LDA QQ29 ; If QQ29 >= 17 then jump to TT168 as we have done the CMP #17 ; last item BCC TT168 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: var ; Type: Subroutine ; Category: Market ; Summary: Calculate QQ19+3 = economy * |economic_factor| ; ; ------------------------------------------------------------------------------ ; ; Set QQ19+3 = economy * |economic_factor|, given byte #1 of the market prices ; table for an item. Also sets the availability of alien items to 0. ; ; This routine forms part of the calculations for market item prices (TT151) ; and availability (GVL). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; QQ19+1 Byte #1 of the market prices table for this market item ; (which contains the economic_factor in bits 0-5, and the ; sign of the economic_factor in bit 7) ; ; ****************************************************************************** .var LDA QQ19+1 ; Extract bits 0-5 from QQ19+1 into A, to get the AND #31 ; economic_factor without its sign, in other words: ; ; A = |economic_factor| LDY QQ28 ; Set Y to the economy byte of the current system STA QQ19+2 ; Store A in QQ19+2 CLC ; Clear the C flag so we can do additions below LDA #0 ; Set AVL+16 (availability of alien items) to 0, STA AVL+16 ; setting A to 0 in the process .TT153 ; We now do the multiplication by doing a series of ; additions in a loop, building the result in A. Each ; loop adds QQ19+2 (|economic_factor|) to A, and it ; loops the number of times given by the economy byte; ; in other words, because A starts at 0, this sets: ; ; A = economy * |economic_factor| DEY ; Decrement the economy in Y, exiting the loop when it BMI TT154 ; becomes negative ADC QQ19+2 ; Add QQ19+2 to A JMP TT153 ; Loop back to TT153 to do another addition .TT154 STA QQ19+3 ; Store the result in QQ19+3 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: hyp1 ; Type: Subroutine ; Category: Universe ; Summary: Process a jump to the system closest to (QQ9, QQ10) ; ; ------------------------------------------------------------------------------ ; ; Do a hyperspace jump to the system closest to galactic coordinates ; (QQ9, QQ10), and set up the current system's state to those of the new system. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; (QQ0, QQ1) The galactic coordinates of the new system ; ; QQ2 to QQ2+6 The seeds of the new system ; ; EV Set to 0 ; ; QQ28 The new system's economy ; ; tek The new system's tech level ; ; gov The new system's government ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; hyp1+3 Jump straight to the system at (QQ9, QQ10) without ; first calculating which system is closest. We do this ; if we already know that (QQ9, QQ10) points to a system ; ; ****************************************************************************** .hyp1 JSR TT111 ; Select the system closest to galactic coordinates ; (QQ9, QQ10) JSR jmp ; Set the current system to the selected system LDX #5 ; We now want to copy the seeds for the selected system ; in QQ15 into QQ2, where we store the seeds for the ; current system, so set up a counter in X for copying ; 6 bytes (for three 16-bit seeds) .TT112 LDA safehouse,X ; Copy the X-th byte in safehouse to the X-th byte in STA QQ2,X ; QQ2 DEX ; Decrement the counter BPL TT112 ; Loop back to TT112 if we still have more bytes to ; copy INX ; Set X = 0 (as we ended the above loop with X = $FF) STX EV ; Set EV, the extra vessels spawning counter, to 0, as ; we are entering a new system with no extra vessels ; spawned LDA QQ3 ; Set the current system's economy in QQ28 to the STA QQ28 ; selected system's economy from QQ3 LDA QQ5 ; Set the current system's tech level in tek to the STA tek ; selected system's economy from QQ5 LDA QQ4 ; Set the current system's government in gov to the STA gov ; selected system's government from QQ4 ; ****************************************************************************** ; ; Name: GVL ; Type: Subroutine ; Category: Universe ; Summary: Calculate the availability of market items ; Deep dive: Market item prices and availability ; Galaxy and system seeds ; ; ------------------------------------------------------------------------------ ; ; Calculate the availability for each market item and store it in AVL. This is ; called on arrival in a new system. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; hyR Contains an RTS ; ; ****************************************************************************** .GVL JSR DORND ; Set A and X to random numbers STA QQ26 ; Set QQ26 to the random byte that's used in the market ; calculations LDX #0 ; We are now going to loop through the market item STX XX4 ; availability table in AVL, so set a counter in XX4 ; (and X) for the market item number, starting with 0 .hy9 LDA QQ23+1,X ; Fetch byte #1 from the market prices table (units and STA QQ19+1 ; economic_factor) for item number X and store it in ; QQ19+1 JSR var ; Call var to set QQ19+3 = economy * |economic_factor| ; (and set the availability of alien items to 0) LDA QQ23+3,X ; Fetch byte #3 from the market prices table (mask) and AND QQ26 ; AND with the random number for this system visit ; to give: ; ; A = random AND mask CLC ; Add byte #2 from the market prices table ADC QQ23+2,X ; (base_quantity) so we now have: ; ; A = base_quantity + (random AND mask) LDY QQ19+1 ; Fetch the byte #1 that we stored above and jump to BMI TT157 ; TT157 if it is negative (i.e. if the economic_factor ; is negative) SEC ; Set A = A - QQ19+3 SBC QQ19+3 ; ; = base_quantity + (random AND mask) ; - (economy * |economic_factor|) ; ; which is the result we want, as the economic_factor ; is positive JMP TT158 ; Jump to TT158 to skip TT157 .TT157 CLC ; Set A = A + QQ19+3 ADC QQ19+3 ; ; = base_quantity + (random AND mask) ; + (economy * |economic_factor|) ; ; which is the result we want, as the economic_factor ; is negative .TT158 BPL TT159 ; If A < 0, then set A = 0, so we don't have negative LDA #0 ; availability .TT159 LDY XX4 ; Fetch the counter (the market item number) into Y AND #%00111111 ; Take bits 0-5 of A, i.e. A mod 64, and store this as STA AVL,Y ; this item's availability in the Y=th byte of AVL, so ; each item has a maximum availability of 63t INY ; Increment the counter into XX44, Y and A TYA STA XX4 ASL A ; Set X = counter * 4, so that X points to the next ASL A ; item's entry in the four-byte market prices table, TAX ; ready for the next loop CMP #63 ; If A < 63, jump back up to hy9 to set the availability BCC hy9 ; for the next market item .hyR RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: GTHG ; Type: Subroutine ; Category: Universe ; Summary: Spawn a Thargoid ship and a Thargon companion ; Deep dive: Fixing ship positions ; Aggression and hostility in ship tactics ; ; ****************************************************************************** .GTHG JSR Ze ; Call Ze to initialise INWK to a fairly aggressive ; ship (though we increase this below) ; ; Note that because Ze uses the value of X returned by ; DORND, and X contains the value of A returned by the ; previous call to DORND, this does not set the new ship ; to a totally random location LDA #%11111111 ; Set the AI flag in byte #32 so that the ship has AI, STA INWK+32 ; an aggression level of 63 out of 63, and E.C.M. LDA #THG ; Call NWSHP to add a new Thargoid ship to our local JSR NWSHP ; bubble of universe LDA #TGL ; Call NWSHP to add a new Thargon ship to our local JMP NWSHP ; bubble of universe, and return from the subroutine ; using a tail call ; ****************************************************************************** ; ; Name: MJP ; Type: Subroutine ; Category: Flight ; Summary: Process a mis-jump into witchspace ; ; ------------------------------------------------------------------------------ ; ; Process a mis-jump into witchspace (which happens very rarely). Witchspace has ; a strange, almost dust-free aspect to it, and it is populated by aggressive ; Thargoids. Using our escape pod will be fatal, and our position on the ; galactic chart is in-between systems. It is a scary place... ; ; There is a 0.78% chance that this routine is called from TT18 instead of doing ; a normal hyperspace, or we can manually trigger a mis-jump by holding down ; CTRL after first enabling the "author display" configuration option ("X") when ; paused. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; ptg Called when the user manually forces a mis-jump ; ; RTS111 Contains an RTS ; ; ****************************************************************************** .ptg LSR COK ; Set bit 0 of the competition flags in COK, so that the SEC ; competition code will include the fact that we have ROL COK ; manually forced a mis-jump into witchspace .MJP ;JSR CATLOD ; This instruction is commented out in the original ; source LDA #3 ; Clear the top part of the screen, draw a border box, JSR TT66 ; and set the current view type in QQ11 to 3 JSR LL164 ; Call LL164 to show the hyperspace tunnel and make the ; hyperspace sound for a second time (as we already ; called LL164 in TT18) JSR RES2 ; Reset a number of flight variables and workspaces, as ; well as setting Y to $FF STY MJ ; Set the mis-jump flag in MJ to $FF, to indicate that ; we are now in witchspace .MJP1 JSR GTHG ; Call GTHG to spawn a Thargoid ship and a Thargon ; companion LDA #3 ; Fetch the number of Thargoid ships from MANY+THG, and CMP MANY+THG ; if it is less than or equal to 3, loop back to MJP1 to BCS MJP1 ; spawn another one, until we have four Thargoids STA NOSTM ; Set NOSTM (the maximum number of stardust particles) ; to 3, so there are fewer bits of stardust in ; witchspace (normal space has a maximum of 18) LDX #0 ; Initialise the front space view JSR LOOK1 LDA QQ1 ; Fetch the current system's galactic y-coordinate in EOR #%00011111 ; QQ1 and flip bits 0-5, so we end up somewhere in the STA QQ1 ; vicinity of our original destination, but above or ; below it in the galactic chart RTS ; Return from the subroutine .RTS111 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TT18 ; Type: Subroutine ; Category: Flight ; Summary: Try to initiate a jump into hyperspace ; ; ------------------------------------------------------------------------------ ; ; Try to go through hyperspace. Called from TT102 in the main loop when the ; hyperspace countdown has finished. ; ; ****************************************************************************** .TT18 LDA QQ14 ; Subtract the distance to the selected system (in QQ8) SEC ; from the amount of fuel in our tank (in QQ14) into A SBC QQ8 BCS P%+4 ; If the subtraction didn't overflow, skip the next ; instruction LDA #0 ; The subtraction overflowed, so set A = 0 so we don't ; end up with a negative amount of fuel STA QQ14 ; Store the updated fuel amount in QQ14 LDA QQ11 ; If the current view is not a space view, jump to ee5 BNE ee5 ; to skip the following JSR TT66 ; Clear the top part of the screen, draw a border box, ; and set the current view type in QQ11 to 0 (space ; view) JSR LL164 ; Call LL164 to show the hyperspace tunnel and make the ; hyperspace sound .ee5 JSR CTRL ; Scan the keyboard to see if CTRL is currently pressed, ; returning a negative value in A if it is AND PATG ; If the game is configured to show the author's names ; on the start-up screen, then PATG will contain $FF, ; otherwise it will be 0 BMI ptg ; By now, A will be negative if we are holding down CTRL ; and author names are configured, which is what we have ; to do in order to trigger a manual mis-jump, so jump ; to ptg to do a mis-jump (ptg not only mis-jumps, but ; updates the competition flags, so Firebird could tell ; from the competition code whether this feature had ; been used) JSR DORND ; Set A and X to random numbers CMP #253 ; If A >= 253 (0.78% chance) then jump to MJP to trigger BCS MJP ; a mis-jump into witchspace ;JSR TT111 ; This instruction is commented out in the original ; source. It finds the closest system to coordinates ; (QQ9, QQ10), but we don't need to do this as the ; crosshairs will already be on a system by this point JSR hyp1+3 ; Jump straight to the system at (QQ9, QQ10) without ; first calculating which system is closest JSR RES2 ; Reset a number of flight variables and workspaces JSR SOLAR ; Halve our legal status, update the missile indicators, ; and set up data blocks and slots for the planet and ; sun ;JSR CATLOD ; These instructions are commented out in the original ; ; source ;JSR LOMOD LDA QQ11 ; If the current view in QQ11 is not a space view (0) or AND #%00111111 ; one of the charts (64 or 128), return from the BNE RTS111 ; subroutine (as RTS111 contains an RTS) JSR TTX66 ; Otherwise clear the screen and draw a border box LDA QQ11 ; If the current view is one of the charts, jump to BNE TT114 ; TT114 (from which we jump to the correct routine to ; display the chart) INC QQ11 ; This is a space view, so increment QQ11 to 1 ; Fall through into TT110 to show the front space view ; ****************************************************************************** ; ; Name: TT110 ; Type: Subroutine ; Category: Flight ; Summary: Launch from a station or show the front space view ; ; ------------------------------------------------------------------------------ ; ; Launch the ship (if we are docked), or show the front space view (if we are ; already in space). ; ; Called when the launch button is pressed while docked, after we arrive in a ; new galaxy, or after a hyperspace if the current view is a space view. ; ; ****************************************************************************** .TT110 LDX QQ12 ; If we are not docked (QQ12 = 0) then jump to NLUNCH BEQ NLUNCH ; to skip the launch tunnel and setup process JSR LAUN ; Show the space station launch tunnel JSR RES2 ; Reset a number of flight variables and workspaces JSR TT111 ; Select the system closest to galactic coordinates ; (QQ9, QQ10) INC INWK+8 ; Increment z_sign ready for the call to SOS, so the ; planet appears at a z_sign of 1 in front of us when ; we launch JSR SOS1 ; Call SOS1 to set up the planet's data block and add it ; to FRIN, where it will get put in the first slot as ; it's the first one to be added to our local bubble of ; universe following the call to RES2 above LDA #128 ; For the space station, set z_sign to $80, so it's STA INWK+8 ; behind us ($80 is negative) INC INWK+7 ; And increment z_hi, so it's only just behind us JSR NWSPS ; Add a new space station to our local bubble of ; universe LDA #12 ; Set our launch speed in DELTA to 12 STA DELTA JSR BAD ; Call BAD to work out how much illegal contraband we ; are carrying in our hold (A is up to 40 for a ; standard hold crammed with contraband, up to 70 for ; an extended cargo hold full of narcotics and slaves) ORA FIST ; OR the value in A with our legal status in FIST to ; get a new value that is at least as high as both ; values, to reflect the fact that launching with a ; hold full of contraband can only make matters worse STA FIST ; Update our legal status with the new value LDA #255 ; Set the view type in QQ11 to 255 STA QQ11 JSR HFS1 ; Call HFS1 to draw 8 concentric rings to remove the ; launch tunnel that we drew above .NLUNCH LDX #0 ; Set QQ12 to 0 to indicate we are not docked STX QQ12 JMP LOOK1 ; Jump to LOOK1 to switch to the front view (X = 0), ; returning from the subroutine using a tail call ; ****************************************************************************** ; ; Name: TT114 ; Type: Subroutine ; Category: Charts ; Summary: Display either the Long-range or Short-range Chart ; ; ------------------------------------------------------------------------------ ; ; Display either the Long-range or Short-range Chart, depending on the current ; view setting. Called from TT18 once we know the current view is one of the ; charts. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The current view, loaded from QQ11 ; ; ****************************************************************************** .TT114 BMI TT115 ; If bit 7 of the current view is set (i.e. the view is ; the Short-range Chart, 128), skip to TT115 below to ; jump to TT23 to display the chart JMP TT22 ; Otherwise the current view is the Long-range Chart, so ; jump to TT22 to display it .TT115 JMP TT23 ; Jump to TT23 to display the Short-range Chart ; ****************************************************************************** ; ; Name: LCASH ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Subtract an amount of cash from the cash pot ; ; ------------------------------------------------------------------------------ ; ; Subtract (Y X) cash from the cash pot in CASH, but only if there is enough ; cash in the pot. As CASH is a four-byte number, this calculates: ; ; CASH(0 1 2 3) = CASH(0 1 2 3) - (0 0 Y X) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag If set, there was enough cash to do the subtraction ; ; If clear, there was not enough cash to do the ; subtraction ; ; ****************************************************************************** .LCASH STX T1 ; Subtract the least significant bytes: LDA CASH+3 ; SEC ; CASH+3 = CASH+3 - X SBC T1 STA CASH+3 STY T1 ; Then the second most significant bytes: LDA CASH+2 ; SBC T1 ; CASH+2 = CASH+2 - Y STA CASH+2 LDA CASH+1 ; Then the third most significant bytes (which are 0): SBC #0 ; STA CASH+1 ; CASH+1 = CASH+1 - 0 LDA CASH ; And finally the most significant bytes (which are 0): SBC #0 ; STA CASH ; CASH = CASH - 0 BCS TT113 ; If the C flag is set then the subtraction didn't ; underflow, so the value in CASH is correct and we can ; jump to TT113 to return from the subroutine with the ; C flag set to indicate success (as TT113 contains an ; RTS) ; Otherwise we didn't have enough cash in CASH to ; subtract (Y X) from it, so fall through into ; MCASH to reverse the sum and restore the original ; value in CASH, and returning with the C flag clear ; ****************************************************************************** ; ; Name: MCASH ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Add an amount of cash to the cash pot ; ; ------------------------------------------------------------------------------ ; ; Add (Y X) cash to the cash pot in CASH. As CASH is a four-byte number, this ; calculates: ; ; CASH(0 1 2 3) = CASH(0 1 2 3) + (Y X) ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; TT113 Contains an RTS ; ; ****************************************************************************** .MCASH TXA ; Add the least significant bytes: CLC ; ADC CASH+3 ; CASH+3 = CASH+3 + X STA CASH+3 TYA ; Then the second most significant bytes: ADC CASH+2 ; STA CASH+2 ; CASH+2 = CASH+2 + Y LDA CASH+1 ; Then the third most significant bytes (which are 0): ADC #0 ; STA CASH+1 ; CASH+1 = CASH+1 + 0 LDA CASH ; And finally the most significant bytes (which are 0): ADC #0 ; STA CASH ; CASH = CASH + 0 CLC ; Clear the C flag, so if the above was done following ; a failed LCASH call, the C flag correctly indicates ; failure .TT113 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: GCASH ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (Y X) = P * Q * 4 ; ; ------------------------------------------------------------------------------ ; ; Calculate the following multiplication of unsigned 8-bit numbers: ; ; (Y X) = P * Q * 4 ; ; ****************************************************************************** .GCASH JSR MULTU ; Call MULTU to calculate (A P) = P * Q ; ****************************************************************************** ; ; Name: GC2 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (Y X) = (A P) * 4 ; ; ------------------------------------------------------------------------------ ; ; Calculate the following multiplication of unsigned 16-bit numbers: ; ; (Y X) = (A P) * 4 ; ; ****************************************************************************** .GC2 ASL P ; Set (A P) = (A P) * 4 ROL A ASL P ROL A TAY ; Set (Y X) = (A P) LDX P RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: RDLI ; Type: Variable ; Category: Loader ; Summary: The OS command string for running the flight code in file D.CODE ; in the disc version of Elite ; Deep dive: Swapping between the docked and flight code ; ; ------------------------------------------------------------------------------ ; ; This command is not used in the Commodore 64 version of Elite; it is left over ; from the BBC Micro disc version. ; ; ****************************************************************************** .RDLI EQUS "R.D.CODE" ; This is short for "*RUN D.CODE" EQUB 13 ; ****************************************************************************** ; ; Name: EQSHP ; Type: Subroutine ; Category: Equipment ; Summary: Show the Equip Ship screen ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; err Beep, pause and go to the docking bay (i.e. show the ; Status Mode screen) ; ; pres Given an item number A with the item name in recursive ; token Y, show an error to say that the item is already ; present, refund the cost of the item, and then beep and ; exit to the docking bay (i.e. show the Status Mode ; screen) ; ; ****************************************************************************** .bay JMP BAY ; Go to the docking bay (i.e. show the Status Mode ; screen) .EQSHP LDA #32 ; Clear the screen, draw a border box, and set up a JSR TRADEMODE ; trading screen with a view type in QQ11 of 32 (Equip ; Ship screen) LDA #12 ; Move the text cursor to column 12 JSR DOXC LDA #207 ; Print recursive token 47 ("EQUIP") followed by a space JSR spc LDA #185 ; Print recursive token 25 ("SHIP") and draw a JSR NLIN3 ; horizontal line at pixel row 19 to box in the title LDA #%10000000 ; Set bit 7 of QQ17 to switch to Sentence Case, with the STA QQ17 ; next letter in capitals JSR INCYC ; Move the text cursor down one line LDA tek ; Fetch the tech level of the current system from tek CLC ; and add 3 (the tech level is stored as 0-14, so A is ADC #3 ; now set to between 3 and 17) CMP #12 ; If A >= 12 then set A = 14, so A is now set to between BCC P%+4 ; 3 and 14 LDA #14 STA Q ; Set QQ25 = A (so QQ25 is in the range 3-14 and STA QQ25 ; represents the number of the most advanced item INC Q ; available in this system, which we can pass to gnum ; below when asking which item we want to buy) ; ; Set Q = A + 1 (so Q is in the range 4-15 and contains ; QQ25 + 1, i.e. the highest item number on sale + 1) LDA #70 ; Set A = 70 - QQ14, where QQ14 contains the current SEC ; fuel in light years * 10, so this leaves the amount SBC QQ14 ; of fuel we need to fill 'er up (in light years * 10) ASL A ; The price of fuel is always 2 Cr per light year, so we STA PRXS ; double A and store it in PRXS, as the first price in ; the price list (which is reserved for fuel), and ; because the table contains prices as price * 10, it's ; in the right format (so tank containing 7.0 light ; years of fuel would be 14.0 Cr, or a PRXS value of ; 140) LDX #1 ; We are now going to work our way through the equipment ; price list at PRXS, printing out the equipment that is ; available at this station, so set a counter in X, ; starting at 1, to hold the number of the current item ; plus 1 (so the item number in X loops through 1-13) .EQL1 STX XX13 ; Store the current item number + 1 in XX13 JSR TT67 ; Print a newline LDX XX13 ; Print the current item number + 1 to 3 digits, left- CLC ; padding with spaces, and with no decimal point, so the JSR pr2 ; items are numbered from 1 JSR TT162 ; Print a space LDA XX13 ; Print recursive token 104 + XX13, which will be in the CLC ; range 105 ("FUEL") to 116 ("GALACTIC HYPERSPACE ") ADC #104 ; so this prints the current item's name JSR TT27 LDA XX13 ; Call prx-3 to set (Y X) to the price of the item with JSR prx-3 ; number XX13 - 1 (as XX13 contains the item number + 1) SEC ; Set the C flag so we will print a decimal point when ; we print the price LDA #25 ; Move the text cursor to column 25 JSR DOXC LDA #6 ; Print the number in (Y X) to 6 digits, left-padding JSR TT11 ; with spaces and including a decimal point, which will ; be the correct price for this item as (Y X) contains ; the price * 10, so the trailing zero will go after the ; decimal point (i.e. 5250 will be printed as 525.0) LDX XX13 ; Increment the current item number in XX13 INX CPX Q ; If X < Q, loop back up to print the next item on the BCC EQL1 ; list of equipment available at this station JSR CLYNS ; Clear the bottom three text rows of the upper screen, ; and move the text cursor to the first cleared row LDA #127 ; Print recursive token 127 ("ITEM") followed by a JSR prq ; question mark JSR gnum ; Call gnum to get a number from the keyboard, which ; will be the number of the item we want to purchase, ; returning the number entered in A and R, and setting ; the C flag if the number is bigger than the highest ; item number in QQ25 BEQ bay ; If no number was entered, jump up to bay to go to the ; docking bay (i.e. show the Status Mode screen) BCS bay ; If the number entered was too big, jump up to bay to ; go to the docking bay (i.e. show the Status Mode ; screen) SBC #0 ; Set A to the number entered - 1 (because the C flag is ; clear), which will be the actual item number we want ; to buy PHA ; Store A on the stack so we can restore it after the ; following call to DOXC LDA #2 ; Move the text cursor to column 2 JSR DOXC JSR INCYC ; Move the text cursor down one line PLA ; Restore A from the stack PHA ; While preserving the value in A, call eq to subtract JSR eq ; the price of the item we want to buy (which is in A) PLA ; from our cash pot, but only if we have enough cash in ; the pot. If we don't have enough cash, exit to the ; docking bay (i.e. show the Status Mode screen) BNE et0 ; If A is not 0 (i.e. the item we've just bought is not ; fuel), skip to et0 LDX #70 ; Set the current fuel level * 10 in QQ14 to 70, or 7.0 STX QQ14 ; light years (a full tank) .et0 CMP #1 ; If A is not 1 (i.e. the item we've just bought is not BNE et1 ; a missile), skip to et1 LDX NOMSL ; Fetch the current number of missiles from NOMSL into X INX ; Increment X to the new number of missiles LDY #124 ; Set Y to recursive token 124 ("ALL") CPX #5 ; If buying this missile would give us 5 missiles, this BCS pres ; is more than the maximum of 4 missiles that we can ; fit, so jump to pres to show the error "All Present", ; beep and exit to the docking bay (i.e. show the Status ; Mode screen) STX NOMSL ; Otherwise update the number of missiles in NOMSL JSR msblob ; Reset the dashboard's missile indicators so none of ; them are targeted LDA #1 ; Set A to 1 as the call to msblob will have overwritten ; the original value, and we still need it set ; correctly so we can continue through the conditional ; statements for all the other equipment .et1 LDY #107 ; Set Y to recursive token 107 ("LARGE CARGO{sentence ; case} BAY") CMP #2 ; If A is not 2 (i.e. the item we've just bought is not BNE et2 ; a large cargo bay), skip to et2 LDX #37 ; If our current cargo capacity in CRGO is 37, then we CPX CRGO ; already have a large cargo bay fitted, so jump to pres BEQ pres ; to show the error "Large Cargo Bay Present", beep and ; exit to the docking bay (i.e. show the Status Mode ; screen) STX CRGO ; Otherwise we just scored ourselves a large cargo bay, ; so update our current cargo capacity in CRGO to 37 .et2 CMP #3 ; If A is not 3 (i.e. the item we've just bought is not BNE et3 ; an E.C.M. system), skip to et3 INY ; Increment Y to recursive token 108 ("E.C.M.SYSTEM") LDX ECM ; If we already have an E.C.M. fitted (i.e. ECM is BNE pres ; non-zero), jump to pres to show the error "E.C.M. ; System Present", beep and exit to the docking bay ; (i.e. show the Status Mode screen) DEC ECM ; Otherwise we just took delivery of a brand new E.C.M. ; system, so set ECM to $FF (as ECM was 0 before the DEC ; instruction) .et3 CMP #4 ; If A is not 4 (i.e. the item we've just bought is not BNE et4 ; an extra pulse laser), skip to et4 JSR qv ; Print a menu listing the four views, with a "View ?" ; prompt, and ask for a view number, which is returned ; in X (which now contains 0-3) LDA #POW ; Call refund with A set to the power of the new pulse JSR refund ; laser to install the new laser and process a refund if ; we already have a laser fitted to this view LDA #4 ; Set A to 4 as we just overwrote the original value, ; and we still need it set correctly so we can continue ; through the conditional statements for all the other ; equipment .et4 CMP #5 ; If A is not 5 (i.e. the item we've just bought is not BNE et5 ; an extra beam laser), skip to et5 JSR qv ; Print a menu listing the four views, with a "View ?" ; prompt, and ask for a view number, which is returned ; in X (which now contains 0-3) LDA #POW+128 ; Call refund with A set to the power of the new beam JSR refund ; laser to install the new laser and process a refund if ; we already have a laser fitted to this view .et5 LDY #111 ; Set Y to recursive token 111 ("FUEL SCOOPS") CMP #6 ; If A is not 6 (i.e. the item we've just bought is not BNE et6 ; a fuel scoop), skip to et6 LDX BST ; If we already have fuel scoops fitted (i.e. BST is BEQ ed9 ; zero), jump to ed9, otherwise fall through into pres ; to show the error "Fuel Scoops Present", beep and ; exit to the docking bay (i.e. show the Status Mode ; screen) .pres ; If we get here we need to show an error to say that ; the item whose name is in recursive token Y is already ; present, and then process a refund for the cost of ; item number A STY K ; Store the item's name in K JSR prx ; Call prx to set (Y X) to the price of equipment item ; number A JSR MCASH ; Add (Y X) cash to the cash pot in CASH, as the station ; already took the money for this item in the JSR eq ; instruction above, but we can't fit the item, so need ; our money back LDA K ; Print the recursive token in K (the item's name) JSR spc ; followed by a space LDA #31 ; Print recursive token 145 ("PRESENT") JSR TT27 .err JSR dn2 ; Call dn2 to make a short, high beep and delay for 1 ; second JMP BAY ; Jump to BAY to go to the docking bay (i.e. show the ; Status Mode screen) .ed9 DEC BST ; We just bought a shiny new fuel scoop, so set BST to ; $FF (as BST was 0 before the jump to ed9 above) .et6 INY ; Increment Y to recursive token 112 ("E.C.M.SYSTEM") CMP #7 ; If A is not 7 (i.e. the item we've just bought is not BNE et7 ; an escape pod), skip to et7 LDX ESCP ; If we already have an escape pod fitted (i.e. ESCP is BNE pres ; non-zero), jump to pres to show the error "Escape Pod ; Present", beep and exit to the docking bay (i.e. show ; the Status Mode screen) DEC ESCP ; Otherwise we just bought an escape pod, so set ESCP ; to $FF (as ESCP was 0 before the DEC instruction) .et7 INY ; Increment Y to recursive token 113 ("ENERGY BOMB") CMP #8 ; If A is not 8 (i.e. the item we've just bought is not BNE et8 ; an energy bomb), skip to et8 LDX BOMB ; If we already have an energy bomb fitted (i.e. BOMB BNE pres ; is non-zero), jump to pres to show the error "Energy ; Bomb Present", beep and exit to the docking bay (i.e. ; show the Status Mode screen) LDX #$7F ; Otherwise we just bought an energy bomb, so set BOMB STX BOMB ; to $7F .et8 INY ; Increment Y to recursive token 114 ("ENERGY UNIT") CMP #9 ; If A is not 9 (i.e. the item we've just bought is not BNE etA ; an energy unit), skip to etA LDX ENGY ; If we already have an energy unit fitted (i.e. ENGY is BNE pres ; non-zero), jump to pres to show the error "Energy Unit ; Present", beep and exit to the docking bay (i.e. show ; the Status Mode screen) INC ENGY ; Otherwise we just picked up an energy unit, so set ; ENGY to 1 (as ENGY was 0 before the INC instruction) .etA INY ; Increment Y to recursive token 115 ("DOCKING ; COMPUTERS") CMP #10 ; If A is not 10 (i.e. the item we've just bought is not BNE etB ; a docking computer), skip to etB LDX DKCMP ; If we already have a docking computer fitted (i.e. BNE pres ; DKCMP is non-zero), jump to pres to show the error ; "Docking Computer Present", beep and exit to the ; docking bay (i.e. show the Status Mode screen) DEC DKCMP ; Otherwise we just got hold of a docking computer, so ; set DKCMP to $FF (as DKCMP was 0 before the DEC ; instruction) .etB INY ; Increment Y to recursive token 116 ("GALACTIC ; HYPERSPACE ") CMP #11 ; If A is not 11 (i.e. the item we've just bought is not BNE et9 ; a galactic hyperdrive), skip to et9 LDX GHYP ; If we already have a galactic hyperdrive fitted (i.e. BNE pres ; GHYP is non-zero), jump to pres to show the error ; "Galactic Hyperspace Present", beep and exit to the ; docking bay (i.e. show the Status Mode screen) DEC GHYP ; Otherwise we just splashed out on a galactic ; hyperdrive, so set GHYP to $FF (as GHYP was 0 before ; the DEC instruction) .et9 INY ; Increment Y to recursive token 117 ("MILITARY LASER") CMP #12 ; If A is not 12 (i.e. the item we've just bought is not BNE et10 ; a military laser), skip to et10 JSR qv ; Print a menu listing the four views, with a "View ?" ; prompt, and ask for a view number, which is returned ; in X (which now contains 0-3) LDA #Armlas ; Call refund with A set to the power of the new JSR refund ; military laser to install the new laser and process a ; refund if we already have a laser fitted to this view .et10 INY ; Increment Y to recursive token 118 ("MINING LASER") CMP #13 ; If A is not 13 (i.e. the item we've just bought is not BNE et11 ; a mining laser), skip to et11 JSR qv ; Print a menu listing the four views, with a "View ?" ; prompt, and ask for a view number, which is returned ; in X (which now contains 0-3) LDA #Mlas ; Call refund with A set to the power of the new mining JSR refund ; laser to install the new laser and process a refund if ; we already have a laser fitted to this view .et11 JSR dn ; We are done buying equipment, so print the amount of ; cash left in the cash pot, then make a short, high ; beep to confirm the purchase, and delay for 1 second JMP EQSHP ; Jump back up to EQSHP to show the Equip Ship screen ; again and see if we can't track down another bargain ; ****************************************************************************** ; ; Name: dn ; Type: Subroutine ; Category: Market ; Summary: Print the amount of money we have left in the cash pot, then make ; a short, high beep and delay for 1 second ; ; ****************************************************************************** .dn JSR TT162 ; Print a space LDA #119 ; Print recursive token 119 ("CASH:{cash} CR{crlf}") JSR spc ; followed by a space ; Fall through into dn2 to make a beep and delay for ; 1 second before returning from the subroutine ; ****************************************************************************** ; ; Name: dn2 ; Type: Subroutine ; Category: Text ; Summary: Make a short, high beep and delay for 1 second ; ; ****************************************************************************** .dn2 JSR BEEP ; Call the BEEP subroutine to make a short, high beep LDY #50 ; Wait for 50/50 of a second (1 second) on PAL systems, JMP DELAY ; or 50/60 of a second (0.83 seconds) on NTSC, and ; return from the subroutine using a tail call ; ****************************************************************************** ; ; Name: eq ; Type: Subroutine ; Category: Equipment ; Summary: Subtract the price of equipment from the cash pot ; ; ------------------------------------------------------------------------------ ; ; If we have enough cash, subtract the price of a specified piece of equipment ; from our cash pot and return from the subroutine. If we don't have enough ; cash, exit to the docking bay (i.e. show the Status Mode screen). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The item number of the piece of equipment (0-11) as ; shown in the table at PRXS ; ; ****************************************************************************** .eq JSR prx ; Call prx to set (Y X) to the price of equipment item ; number A JSR LCASH ; Subtract (Y X) cash from the cash pot, but only if ; we have enough cash BCS c ; If the C flag is set then we did have enough cash for ; the transaction, so jump to c to return from the ; subroutine (as c contains an RTS) LDA #197 ; Otherwise we don't have enough cash to buy this piece JSR prq ; of equipment, so print recursive token 37 ("CASH") ; followed by a question mark JMP err ; Jump to err to beep, pause and go to the docking bay ; (i.e. show the Status Mode screen) ; ****************************************************************************** ; ; Name: prx ; Type: Subroutine ; Category: Equipment ; Summary: Return the price of a piece of equipment ; ; ------------------------------------------------------------------------------ ; ; This routine returns the price of equipment as listed in the table at PRXS. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The item number of the piece of equipment (0-13) as ; shown in the table at PRXS ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; (Y X) The item price in Cr * 10 (Y = high byte, X = low byte) ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; prx-3 Return the price of the item with number A - 1 ; ; c Contains an RTS ; ; ****************************************************************************** SEC ; Decrement A (for when this routine is called via SBC #1 ; prx-3) .prx ASL A ; Set Y = A * 2, so it can act as an index into the TAY ; PRXS table, which has two bytes per entry LDX PRXS,Y ; Fetch the low byte of the price into X LDA PRXS+1,Y ; Fetch the high byte of the price into A and transfer TAY ; it to X, so the price is now in (Y X) .c RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: qv ; Type: Subroutine ; Category: Equipment ; Summary: Print a menu of the four space views, for buying lasers ; ; ------------------------------------------------------------------------------ ; ; Print a menu in the bottom-middle of the screen, at row 16, column 12, that ; lists the four available space views, like this: ; ; 0 Front ; 1 Rear ; 2 Left ; 3 Right ; ; Also print a "View ?" prompt and ask for a view number. The menu is shown ; when we choose to buy a new laser in the Equip Ship screen. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; X The chosen view number (0-3) ; ; ****************************************************************************** .qv LDA tek ; If the current system's tech level is less than 8, CMP #8 ; skip the next two instructions, otherwise we clear the BCC P%+7 ; screen to prevent the view menu from clashing with the ; longer equipment menu available in higher tech systems LDA #32 ; Clear the screen, draw a border box, and set the JSR TT66 ; current view type in QQ11 to 32 (Equip Ship screen) LDA #16 ; Move the text cursor to row 16, and at the same time TAY ; set Y to a counter going from 16 to 19 in the loop JSR DOYC ; below .qv1 LDA #12 ; Move the text cursor to column 12 JSR DOXC TYA ; Transfer the counter value from Y to A CLC ; Print ASCII character "0" - 16 + A, so as A goes from ADC #'0'-16 ; 16 to 19, this prints "0" through "3" followed by a JSR spc ; space LDA YC ; Print recursive text token 80 + YC, so as YC goes from CLC ; 16 to 19, this prints "FRONT", "REAR", "LEFT" and ADC #80 ; "RIGHT" JSR TT27 JSR INCYC ; Move the text cursor down a row, and increment the ; counter in YC at the same time LDY YC ; Update Y with the incremented counter in YC CPY #20 ; If Y < 20 then loop back up to qv1 to print the next BCC qv1 ; view in the menu JSR CLYNS ; Clear the bottom three text rows of the upper screen, ; and move the text cursor to the first cleared row .qv2 LDA #175 ; Print recursive text token 15 ("VIEW ") followed by JSR prq ; a question mark JSR TT217 ; Scan the keyboard until a key is pressed, and return ; the key's ASCII code in A (and X) SEC ; Subtract ASCII "0" from the key pressed, to leave the SBC #'0' ; numeric value of the key in A (if it was a number key) CMP #4 ; If the number entered in A < 4, then it is a valid BCC qv3 ; view number, so jump down to qv3 as we are done JSR CLYNS ; Otherwise we didn't get a valid view number, so clear ; the bottom three text rows of the upper screen, and ; move the text cursor to column 1 on row 21 JMP qv2 ; Jump back to qv2 to try again .qv3 TAX ; We have a valid view number, so transfer it to X RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: hm ; Type: Subroutine ; Category: Charts ; Summary: Select the closest system and redraw the chart crosshairs ; ; ------------------------------------------------------------------------------ ; ; Set the system closest to galactic coordinates (QQ9, QQ10) as the selected ; system, redraw the crosshairs on the chart accordingly (if they are being ; shown), and if this is not the space view, clear the bottom three text rows of ; the screen. ; ; ****************************************************************************** .hm JSR TT103 ; Draw small crosshairs at coordinates (QQ9, QQ10), ; which will erase the crosshairs currently there JSR TT111 ; Select the system closest to galactic coordinates ; (QQ9, QQ10) JSR TT103 ; Draw small crosshairs at coordinates (QQ9, QQ10), ; which will draw the crosshairs at our current home ; system JMP CLYNS ; Clear the bottom three text rows of the upper screen, ; move the text cursor to the first cleared row, and ; return from the subroutine using a tail call ; ****************************************************************************** ; ; Name: refund ; Type: Subroutine ; Category: Equipment ; Summary: Install a new laser, processing a refund if applicable ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The power of the new laser to be fitted ; ; X The view number for fitting the new laser (0-3) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A is preserved ; ; X X is preserved ; ; ****************************************************************************** ;.ref2 ; These instructions are commented out in the original ; ; source, but they would jump to pres in the EQSHP ;LDY #187 ; routine with Y = 187, which would show the error: ;JMP pres ; "LASER PRESENT" (this code was part of the refund ; ; bug in the BBC Micro disc version of Elite, which ;Belgium ; is why it is commented out) ; ; There is also a comment in the original source - the ; solitary word "Belgium" ; ; This is presumably a reference to the Hitchhiker's ; Guide to the Galaxy, which says that Belgium is the ; galaxy's most unspeakably rude word, so this no doubt ; reflects the authors' strong feelings on the refund ; bug .refund STA T1 ; Store A in T1 so we can retrieve it later LDA LASER,X ; If there is no laser in view X (i.e. the laser power BEQ ref3 ; is zero), jump to ref3 to skip the refund code ;CMP T1 ; These instructions are commented out in the original ;BEQ ref2 ; source, but they would jump to ref2 above if we were ; trying to replace a laser with one of the same type ; (this code was part of the refund bug in the BBC Micro ; disc version of Elite, which is why it is commented ; out) LDY #4 ; If the current laser has power #POW (pulse laser), CMP #POW ; jump to ref1 with Y = 4 (the item number of a pulse BEQ ref1 ; laser in the table at PRXS) LDY #5 ; If the current laser has power #POW+128 (beam laser), CMP #POW+128 ; jump to ref1 with Y = 5 (the item number of a beam BEQ ref1 ; laser in the table at PRXS) LDY #12 ; If the current laser has power #Armlas (military CMP #Armlas ; laser), jump to ref1 with Y = 12 (the item number of a BEQ ref1 ; military laser in the table at PRXS) LDY #13 ; Otherwise this is a mining laser, so fall through into ; ref1 with Y = 13 (the item number of a mining laser in ; the table at PRXS) .ref1 ; We now want to refund the laser of type Y that we are ; exchanging for the new laser STX ZZ ; Store the view number in ZZ so we can retrieve it ; later TYA ; Copy the laser type to be refunded from Y to A JSR prx ; Call prx to set (Y X) to the price of equipment item ; number A JSR MCASH ; Call MCASH to add (Y X) to the cash pot LDX ZZ ; Retrieve the view number from ZZ .ref3 ; Finally, we install the new laser LDA T1 ; Retrieve the new laser's power from T1 into A STA LASER,X ; Set the laser view to the new laser's power RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: PRXS ; Type: Variable ; Category: Equipment ; Summary: Equipment prices ; ; ------------------------------------------------------------------------------ ; ; Equipment prices are stored as 10 * the actual value, so we can support prices ; with fractions of credits (0.1 Cr). This is used for the price of fuel only. ; ; ****************************************************************************** .PRXS EQUW 1 ; 0 Fuel, calculated in EQSHP 140.0 Cr (full tank) EQUW 300 ; 1 Missile 30.0 Cr EQUW 4000 ; 2 Large Cargo Bay 400.0 Cr EQUW 6000 ; 3 E.C.M. System 600.0 Cr EQUW 4000 ; 4 Extra Pulse Lasers 400.0 Cr EQUW 10000 ; 5 Extra Beam Lasers 1000.0 Cr EQUW 5250 ; 6 Fuel Scoops 525.0 Cr EQUW 10000 ; 7 Escape Pod 1000.0 Cr EQUW 9000 ; 8 Energy Bomb 900.0 Cr EQUW 15000 ; 9 Energy Unit 1500.0 Cr EQUW 10000 ; 10 Docking Computer 1000.0 Cr EQUW 50000 ; 11 Galactic Hyperspace 5000.0 Cr EQUW 60000 ; 12 Extra Military Lasers 6000.0 Cr EQUW 8000 ; 13 Extra Mining Lasers 800.0 Cr ; ****************************************************************************** ; ; Save ELTD.bin ; ; ****************************************************************************** PRINT "ELITE D" PRINT "Assembled at ", ~CODE_D% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_D%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_D% PRINT "S.ELTD ", ~CODE_D%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_D% SAVE "3-assembled-output/ELTD.bin", CODE_D%, P%, LOAD% ; ****************************************************************************** ; ; ELITE E FILE ; ; Produces the binary file ELTE.bin that gets loaded by elite-checksum.py. ; ; ****************************************************************************** CODE_E% = P% LOAD_E% = LOAD% + P% - CODE% ; ****************************************************************************** ; ; Name: cpl ; Type: Subroutine ; Category: Universe ; Summary: Print the selected system name ; Deep dive: Generating system names ; Galaxy and system seeds ; ; ------------------------------------------------------------------------------ ; ; Print control code 3 (the selected system name, i.e. the one in the crosshairs ; in the Short-range Chart). ; ; ****************************************************************************** .cpl LDX #5 ; First we need to back up the seeds in QQ15, so set up ; a counter in X to cover three 16-bit seeds (i.e. ; 6 bytes) .TT53 LDA QQ15,X ; Copy byte X from QQ15 to QQ19 STA QQ19,X DEX ; Decrement the loop counter BPL TT53 ; Loop back for the next byte to back up LDY #3 ; Step 1: Now that the seeds are backed up, we can ; start the name-generation process. We will either ; need to loop three or four times, so for now set ; up a counter in Y to loop four times BIT QQ15 ; Check bit 6 of s0_lo, which is stored in QQ15 BVS P%+3 ; If bit 6 is set then skip over the next instruction DEY ; Bit 6 is clear, so we only want to loop three times, ; so decrement the loop counter in Y STY T ; Store the loop counter in T .TT55 LDA QQ15+5 ; Step 2: Load s2_hi, which is stored in QQ15+5, and AND #%00011111 ; extract bits 0-4 by AND'ing with %11111 BEQ P%+7 ; If all those bits are zero, then skip the following ; two instructions to go to step 3 ORA #%10000000 ; We now have a number in the range 1-31, which we can ; easily convert into a two-letter token, but first we ; need to add 128 (or set bit 7) to get a range of ; 129-159 JSR TT27 ; Print the two-letter token in A JSR TT54 ; Step 3: twist the seeds in QQ15 DEC T ; Decrement the loop counter BPL TT55 ; Loop back for the next two letters LDX #5 ; We have printed the system name, so we can now ; restore the seeds we backed up earlier. Set up a ; counter in X to cover three 16-bit seeds (i.e. 6 ; bytes) .TT56 LDA QQ19,X ; Copy byte X from QQ19 to QQ15 STA QQ15,X DEX ; Decrement the loop counter BPL TT56 ; Loop back for the next byte to restore RTS ; Once all the seeds are restored, return from the ; subroutine ; ****************************************************************************** ; ; Name: cmn ; Type: Subroutine ; Category: Status ; Summary: Print the commander's name ; ; ------------------------------------------------------------------------------ ; ; Print control code 4 (the commander's name). ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; cmn-1 Contains an RTS ; ; ****************************************************************************** .cmn LDY #0 ; Set up a counter in Y, starting from 0 .QUL4 LDA NAME,Y ; The commander's name is stored at NAME, so load the ; Y-th character from NAME CMP #13 ; If we have reached the end of the name, return from BEQ ypl-1 ; the subroutine (ypl-1 points to the RTS below) JSR TT26 ; Print the character we just loaded INY ; Increment the loop counter BNE QUL4 ; Loop back for the next character RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: ypl ; Type: Subroutine ; Category: Universe ; Summary: Print the current system name ; ; ------------------------------------------------------------------------------ ; ; Print control code 2 (the current system name). ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; ypl-1 Contains an RTS ; ; ****************************************************************************** .ypl BIT MJ ; Check the mis-jump flag at MJ, and if bit 7 is set BMI ypl16 ; then we are in witchspace, and witchspace doesn't have ; a system name, so jump to ypl16 to return from the ; subroutine JSR TT62 ; Call TT62 below to swap the three 16-bit seeds in ; QQ2 and QQ15 (before the swap, QQ2 contains the seeds ; for the current system, while QQ15 contains the seeds ; for the selected system) JSR cpl ; Call cpl to print out the system name for the seeds ; in QQ15 (which now contains the seeds for the current ; system) ; Now we fall through into the TT62 subroutine, which ; will swap QQ2 and QQ15 once again, so everything goes ; back into the right place, and the RTS at the end of ; TT62 will return from the subroutine .TT62 LDX #5 ; Set up a counter in X for the three 16-bit seeds we ; want to swap (i.e. 6 bytes) .TT78 LDA QQ15,X ; Swap byte X between QQ2 and QQ15 LDY QQ2,X STA QQ2,X STY QQ15,X DEX ; Decrement the loop counter BPL TT78 ; Loop back for the next byte to swap .ypl16 RTS ; Once all bytes are swapped, return from the ; subroutine ; ****************************************************************************** ; ; Name: tal ; Type: Subroutine ; Category: Universe ; Summary: Print the current galaxy number ; ; ------------------------------------------------------------------------------ ; ; Print control code 1 (the current galaxy number, right-aligned to width 3). ; ; ****************************************************************************** .tal CLC ; We don't want to print the galaxy number with a ; decimal point, so clear the C flag for pr2 to take as ; an argument LDX GCNT ; Load the current galaxy number from GCNT into X INX ; Add 1 to the galaxy number, as the galaxy numbers ; are 0-7 internally, but we want to display them as ; galaxy 1 through 8 JMP pr2 ; Jump to pr2, which prints the number in X to a width ; of 3 figures, left-padding with spaces to a width of ; 3, and return from the subroutine using a tail call ; ****************************************************************************** ; ; Name: fwl ; Type: Subroutine ; Category: Status ; Summary: Print fuel and cash levels ; ; ------------------------------------------------------------------------------ ; ; Print control code 5 ("FUEL: ", fuel level, " LIGHT YEARS", newline, "CASH:", ; control code 0). ; ; ****************************************************************************** .fwl LDA #105 ; Print recursive token 105 ("FUEL") followed by a JSR TT68 ; colon LDX QQ14 ; Load the current fuel level from QQ14 SEC ; We want to print the fuel level with a decimal point, ; so set the C flag for pr2 to take as an argument JSR pr2 ; Call pr2, which prints the number in X to a width of ; 3 figures (i.e. in the format x.x, which will always ; be exactly 3 characters as the maximum fuel is 7.0) LDA #195 ; Print recursive token 35 ("LIGHT YEARS") followed by JSR plf ; a newline .PCASH LDA #119 ; Print recursive token 119 ("CASH:" then control code BNE TT27 ; 0, which prints cash levels, then " CR" and newline) ; ****************************************************************************** ; ; Name: csh ; Type: Subroutine ; Category: Status ; Summary: Print the current amount of cash ; ; ------------------------------------------------------------------------------ ; ; Print control code 0 (the current amount of cash, right-aligned to width 9, ; followed by " CR" and a newline). ; ; ****************************************************************************** .csh LDX #3 ; We are going to use the BPRNT routine to print out ; the current amount of cash, which is stored as a ; 32-bit number at location CASH. BPRNT prints out ; the 32-bit number stored in K, so before we call ; BPRNT, we need to copy the four bytes from CASH into ; K, so first we set up a counter in X for the 4 bytes .pc1 LDA CASH,X ; Copy byte X from CASH to K STA K,X DEX ; Decrement the loop counter BPL pc1 ; Loop back for the next byte to copy LDA #9 ; We want to print the cash amount using up to 9 digits STA U ; (including the decimal point), so store this in U ; for BRPNT to take as an argument SEC ; We want to print the cash amount with a decimal point, ; so set the C flag for BRPNT to take as an argument JSR BPRNT ; Print the amount of cash to 9 digits with a decimal ; point LDA #226 ; Print recursive token 66 (" CR") followed by a ; newline by falling through into plf ; ****************************************************************************** ; ; Name: plf ; Type: Subroutine ; Category: Text ; Summary: Print a text token followed by a newline ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text token to be printed ; ; ****************************************************************************** .plf JSR TT27 ; Print the text token in A JMP TT67 ; Jump to TT67 to print a newline and return from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: TT68 ; Type: Subroutine ; Category: Text ; Summary: Print a text token followed by a colon ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text token to be printed ; ; ****************************************************************************** .TT68 JSR TT27 ; Print the text token in A and fall through into TT73 ; to print a colon ; ****************************************************************************** ; ; Name: TT73 ; Type: Subroutine ; Category: Text ; Summary: Print a colon ; ; ****************************************************************************** .TT73 LDA #':' ; Set A to ASCII ":" and fall through into TT27 to ; actually print the colon ; ****************************************************************************** ; ; Name: TT27 ; Type: Subroutine ; Category: Text ; Summary: Print a text token ; Deep dive: Printing text tokens ; ; ------------------------------------------------------------------------------ ; ; Print a text token (i.e. a character, control code, two-letter token or ; recursive token). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text token to be printed ; ; ****************************************************************************** .TT27 TAX ; Copy the token number from A to X. We can then keep ; decrementing X and testing it against zero, while ; keeping the original token number intact in A; this ; effectively implements a switch statement on the ; value of the token BEQ csh ; If token = 0, this is control code 0 (current amount ; of cash and newline), so jump to csh to print the ; amount of cash and return from the subroutine using ; a tail call BMI TT43 ; If token > 127, this is either a two-letter token ; (128-159) or a recursive token (160-255), so jump ; to TT43 to process tokens DEX ; If token = 1, this is control code 1 (current galaxy BEQ tal ; number), so jump to tal to print the galaxy number and ; return from the subroutine using a tail call DEX ; If token = 2, this is control code 2 (current system BEQ ypl ; name), so jump to ypl to print the current system name ; and return from the subroutine using a tail call DEX ; If token > 3, skip the following instruction BNE P%+5 JMP cpl ; This token is control code 3 (selected system name) ; so jump to cpl to print the selected system name ; and return from the subroutine using a tail call DEX ; If token = 4, this is control code 4 (commander BEQ cmn ; name), so jump to cmn to print the commander name ; and return from the subroutine using a tail call DEX ; If token = 5, this is control code 5 (fuel, newline, BEQ fwl ; cash, newline), so jump to fwl to print the fuel level ; and return from the subroutine using a tail call DEX ; If token > 6, skip the following three instructions BNE P%+7 LDA #%10000000 ; This token is control code 6 (switch to Sentence STA QQ17 ; Case), so set bit 7 of QQ17 to switch to Sentence Case RTS ; and return from the subroutine as we are done DEX ; If token > 8, skip the following two instructions DEX BNE P%+5 STX QQ17 ; This token is control code 8 (switch to ALL CAPS), so RTS ; set QQ17 to 0 to switch to ALL CAPS and return from ; the subroutine as we are done DEX ; If token = 9, this is control code 9 (tab to column BEQ crlf ; 21 and print a colon), so jump to crlf CMP #96 ; By this point, token is either 7, or in 10-127. BCS ex ; Check token number in A and if token >= 96, then the ; token is in 96-127, which is a recursive token, so ; jump to ex, which prints recursive tokens in this ; range (i.e. where the recursive token number is ; correct and doesn't need correcting) CMP #14 ; If token < 14, skip the following two instructions BCC P%+6 CMP #32 ; If token < 32, then this means token is in 14-31, so BCC qw ; this is a recursive token that needs 114 adding to it ; to get the recursive token number, so jump to qw ; which will do this ; By this point, token is either 7 (beep) or in 10-13 ; (line feeds and carriage returns), or in 32-95 ; (ASCII letters, numbers and punctuation) LDX QQ17 ; Fetch QQ17, which controls letter case, into X BEQ TT74 ; If QQ17 = 0, then ALL CAPS is set, so jump to TT74 ; to print this character as is (i.e. as a capital) BMI TT41 ; If QQ17 has bit 7 set, then we are using Sentence ; Case, so jump to TT41, which will print the ; character in upper or lower case, depending on ; whether this is the first letter in a word BIT QQ17 ; If we get here, QQ17 is not 0 and bit 7 is clear, so BVS TT46 ; either it is bit 6 that is set, or some other flag in ; QQ17 is set (bits 0-5). So check whether bit 6 is set. ; If it is, then ALL CAPS has been set (as bit 7 is ; clear) but bit 6 is still indicating that the next ; character should be printed in lower case, so we need ; to fix this. We do this with a jump to TT46, which ; will print this character in upper case and clear bit ; 6, so the flags are consistent with ALL CAPS going ; forward ; If we get here, some other flag is set in QQ17 (one ; of bits 0-5 is set), which shouldn't happen in this ; version of Elite. If this were the case, then we ; would fall through into TT42 to print in lower case, ; which is how printing all words in lower case could ; be supported (by setting QQ17 to 1, say) ; ****************************************************************************** ; ; Name: TT42 ; Type: Subroutine ; Category: Text ; Summary: Print a letter in lower case ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The character to be printed. Can be one of the ; following: ; ; * 7 (beep) ; ; * 10-13 (line feeds and carriage returns) ; ; * 32-95 (ASCII capital letters, numbers and ; punctuation) ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; TT44 Jumps to TT26 to print the character in A (used to ; enable us to use a branch instruction to jump to TT26) ; ; ****************************************************************************** .TT42 CMP #'A' ; If A < ASCII "A", then this is punctuation, so jump BCC TT44 ; to TT26 (via TT44) to print the character as is, as ; we don't care about the character's case CMP #'Z'+1 ; If A >= (ASCII "Z" + 1), then this is also BCS TT44 ; punctuation, so jump to TT26 (via TT44) to print the ; character as is, as we don't care about the ; character's case ADC #32 ; Add 32 to the character, to convert it from upper to ; lower case .TT44 JMP TT26 ; Print the character in A ; ****************************************************************************** ; ; Name: TT41 ; Type: Subroutine ; Category: Text ; Summary: Print a letter according to Sentence Case ; ; ------------------------------------------------------------------------------ ; ; The rules for printing in Sentence Case are as follows: ; ; * If QQ17 bit 6 is set, print lower case (via TT45) ; ; * If QQ17 bit 6 is clear, then: ; ; * If character is punctuation, just print it ; ; * If character is a letter, set QQ17 bit 6 and print letter as a capital ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The character to be printed. Can be one of the ; following: ; ; * 7 (beep) ; ; * 10-13 (line feeds and carriage returns) ; ; * 32-95 (ASCII capital letters, numbers and ; punctuation) ; ; X Contains the current value of QQ17 ; ; QQ17 Bit 7 is set ; ; ****************************************************************************** .TT41 ; If we get here, then QQ17 has bit 7 set, so we are in ; Sentence Case BIT QQ17 ; If QQ17 also has bit 6 set, jump to TT45 to print BVS TT45 ; this character in lower case ; If we get here, then QQ17 has bit 6 clear and bit 7 ; set, so we are in Sentence Case and we need to print ; the next letter in upper case CMP #'A' ; If A < ASCII "A", then this is punctuation, so jump BCC TT74 ; to TT26 (via TT44) to print the character as is, as ; we don't care about the character's case PHA ; Otherwise this is a letter, so store the token number TXA ; Set bit 6 in QQ17 (X contains the current QQ17) ORA #%1000000 ; so the next letter after this one is printed in lower STA QQ17 ; case PLA ; Restore the token number into A BNE TT44 ; Jump to TT26 (via TT44) to print the character in A ; (this BNE is effectively a JMP as A will never be ; zero) ; ****************************************************************************** ; ; Name: qw ; Type: Subroutine ; Category: Text ; Summary: Print a recursive token in the range 128-145 ; ; ------------------------------------------------------------------------------ ; ; Print a recursive token where the token number is in 128-145 (so the value ; passed to TT27 is in the range 14-31). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A A value from 128-145, which refers to a recursive token ; in the range 14-31 ; ; ****************************************************************************** .qw ADC #114 ; This is a recursive token in the range 0-95, so add BNE ex ; 114 to the argument to get the token number 128-145 ; and jump to ex to print it ; ****************************************************************************** ; ; Name: crlf ; Type: Subroutine ; Category: Text ; Summary: Tab to column 21 and print a colon ; ; ------------------------------------------------------------------------------ ; ; Print control code 9 (tab to column 21 and print a colon). The subroutine ; name is pretty misleading, as it doesn't have anything to do with carriage ; returns or line feeds. ; ; ****************************************************************************** .crlf LDA #21 ; Set the X-column in XC to 21 JSR DOXC JMP TT73 ; Jump to TT73, which prints a colon (this BNE is ; effectively a JMP as A will never be zero) ; ****************************************************************************** ; ; Name: TT45 ; Type: Subroutine ; Category: Text ; Summary: Print a letter in lower case ; ; ------------------------------------------------------------------------------ ; ; This routine prints a letter in lower case. Specifically: ; ; * If QQ17 = 255, abort printing this character as printing is disabled ; ; * If this is a letter then print in lower case ; ; * Otherwise this is punctuation, so clear bit 6 in QQ17 and print ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The character to be printed. Can be one of the ; following: ; ; * 7 (beep) ; ; * 10-13 (line feeds and carriage returns) ; ; * 32-95 (ASCII capital letters, numbers and ; punctuation) ; ; X Contains the current value of QQ17 ; ; QQ17 Bits 6 and 7 are set ; ; ****************************************************************************** .TT45 ; If we get here, then QQ17 has bit 6 and 7 set, so we ; are in Sentence Case and we need to print the next ; letter in lower case CPX #255 ; If QQ17 = 255 then printing is disabled, so return BEQ TT48 ; from the subroutine (as TT48 contains an RTS) CMP #'A' ; If A >= ASCII "A", then jump to TT42, which will BCS TT42 ; print the letter in lowercase ; Otherwise this is not a letter, it's punctuation, so ; this is effectively a word break. We therefore fall ; through to TT46 to print the character and set QQ17 ; to ensure the next word starts with a capital letter ; ****************************************************************************** ; ; Name: TT46 ; Type: Subroutine ; Category: Text ; Summary: Print a character and switch to capitals ; ; ------------------------------------------------------------------------------ ; ; Print a character and clear bit 6 in QQ17, so that the next letter that gets ; printed after this will start with a capital letter. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The character to be printed. Can be one of the ; following: ; ; * 7 (beep) ; ; * 10-13 (line feeds and carriage returns) ; ; * 32-95 (ASCII capital letters, numbers and ; punctuation) ; ; X Contains the current value of QQ17 ; ; QQ17 Bits 6 and 7 are set ; ; ****************************************************************************** .TT46 PHA ; Store the token number TXA ; Clear bit 6 in QQ17 (X contains the current QQ17) so AND #%10111111 ; the next letter after this one is printed in upper STA QQ17 ; case PLA ; Restore the token number into A ; Now fall through into TT74 to print the character ; ****************************************************************************** ; ; Name: TT74 ; Type: Subroutine ; Category: Text ; Summary: Print a character ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The character to be printed ; ; ****************************************************************************** .TT74 JMP TT26 ; Print the character in A ; ****************************************************************************** ; ; Name: TT43 ; Type: Subroutine ; Category: Text ; Summary: Print a two-letter token or recursive token 0-95 ; ; ------------------------------------------------------------------------------ ; ; Print a two-letter token, or a recursive token where the token number is in ; 0-95 (so the value passed to TT27 is in the range 160-255). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A One of the following: ; ; * 128-159 (two-letter token) ; ; * 160-255 (the argument to TT27 that refers to a ; recursive token in the range 0-95) ; ; ****************************************************************************** .TT43 CMP #160 ; If token >= 160, then this is a recursive token, so BCS TT47 ; jump to TT47 below to process it AND #127 ; This is a two-letter token with number 128-159. The ASL A ; set of two-letter tokens is stored in a lookup table ; at QQ16, with each token taking up two bytes, so to ; convert this into the token's position in the table, ; we subtract 128 (or just clear bit 7) and multiply ; by 2 (or shift left) TAY ; Transfer the token's position into Y so we can look ; up the token using absolute indexed mode LDA QQ16,Y ; Get the first letter of the token and print it JSR TT27 LDA QQ16+1,Y ; Get the second letter of the token CMP #'?' ; If the second letter of the token is a question mark BEQ TT48 ; then this is a one-letter token, so just return from ; the subroutine without printing (as TT48 contains an ; RTS) JMP TT27 ; Print the second letter and return from the ; subroutine .TT47 SBC #160 ; This is a recursive token in the range 160-255, so ; subtract 160 from the argument to get the token ; number 0-95 and fall through into ex to print it ; ****************************************************************************** ; ; Name: ex ; Type: Subroutine ; Category: Text ; Summary: Print a recursive token ; Deep dive: Printing text tokens ; ; ------------------------------------------------------------------------------ ; ; This routine works its way through the recursive text tokens that are stored ; in tokenised form in the table at QQ18, and when it finds token number A, ; it prints it. Tokens are null-terminated in memory and fill three pages, ; but there is no lookup table as that would consume too much memory, so the ; only way to find the correct token is to start at the beginning and look ; through the table byte by byte, counting tokens as we go until we are in the ; right place. This approach might not be terribly speed efficient, but it is ; certainly memory-efficient. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The recursive token to be printed, in the range 0-148 ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; TT48 Contains an RTS ; ; ****************************************************************************** .ex TAX ; Copy the token number into X LDA #LO(QQ18) ; Set V(1 0) to point to the recursive token table at STA V ; location QQ18 LDA #HI(QQ18) STA V+1 LDY #0 ; Set a counter Y to point to the character offset ; as we scan through the table TXA ; Copy the token number back into A, so both A and X ; now contain the token number we want to print BEQ TT50 ; If the token number we want is 0, then we have ; already found the token we are looking for, so jump ; to TT50, otherwise start working our way through the ; null-terminated token table until we find the X-th ; token .TT51 LDA (V),Y ; Fetch the Y-th character from the token table page ; we are currently scanning BEQ TT49 ; If the character is null, we've reached the end of ; this token, so jump to TT49 INY ; Increment character pointer and loop back around for BNE TT51 ; the next character in this token, assuming Y hasn't ; yet wrapped around to 0 INC V+1 ; If it has wrapped round to 0, we have just crossed BNE TT51 ; into a new page, so increment V+1 so that V points ; to the start of the new page .TT49 INY ; Increment the character pointer BNE TT59 ; If Y hasn't just wrapped around to 0, skip the next ; instruction INC V+1 ; We have just crossed into a new page, so increment ; V+1 so that V points to the start of the new page .TT59 DEX ; We have just reached a new token, so decrement the ; token number we are looking for BNE TT51 ; Assuming we haven't yet reached the token number in ; X, look back up to keep fetching characters .TT50 ; We have now reached the correct token in the token ; table, with Y pointing to the start of the token as ; an offset within the page pointed to by V, so let's ; print the recursive token. Because recursive tokens ; can contain other recursive tokens, we need to store ; our current state on the stack, so we can retrieve ; it after printing each character in this token TYA ; Store the offset in Y on the stack PHA LDA V+1 ; Store the high byte of V (the page containing the PHA ; token we have found) on the stack, so the stack now ; contains the address of the start of this token LDA (V),Y ; Load the character at offset Y in the token table, ; which is the next character of this token that we ; want to print EOR #RE ; Tokens are stored in memory having been EOR'd with the ; value of RE - which is 35 for all versions of Elite ; except for NES, where RE is 62 - so we repeat the ; EOR to get the actual character to print JSR TT27 ; Print the text token in A, which could be a letter, ; number, control code, two-letter token or another ; recursive token PLA ; Restore the high byte of V (the page containing the STA V+1 ; token we have found) into V+1 PLA ; Restore the offset into Y TAY INY ; Increment Y to point to the next character in the ; token we are printing BNE P%+4 ; If Y is zero then we have just crossed into a new INC V+1 ; page, so increment V+1 so that V points to the start ; of the new page LDA (V),Y ; Load the next character we want to print into A BNE TT50 ; If this is not the null character at the end of the ; token, jump back up to TT50 to print the next ; character, otherwise we are done printing .TT48 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: SWAPPZERO ; Type: Subroutine ; Category: Utility routines ; Summary: A routine that swaps zero page with the page at $CE00, so that ; zero page changes made by Kernal functions can be reversed ; ; ****************************************************************************** IF _GMA_RELEASE .SWAPPZERO LDX #K3+1 ; This routine starts copying zero page from the byte ; after K3 and up, using X as an index .SWPZL LDA ZP,X ; Swap the X-th byte of zero page with the X-th byte of LDY $CE00,X ; $CE00 STA $CE00,X STY ZP,X INX ; Increment the loop counter BNE SWPZL ; Loop back for the next byte RTS ; Return from the subroutine ENDIF ; ****************************************************************************** ; ; Name: DOEXP ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw an exploding ship ; Deep dive: Drawing explosion clouds ; Generating random numbers ; Sprite usage in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; EXS1 Set (A X) = (A R) +/- random * cloud size ; ; ****************************************************************************** .EX2 LDA INWK+31 ; Set bits 5 and 7 of the ship's byte #31 to denote that ORA #%10100000 ; the ship is exploding and has been killed STA INWK+31 RTS ; Return from the subroutine .DOEXP LDA INWK+31 ; If bit 6 of the ship's byte #31 is clear, then the AND #%01000000 ; ship is not already exploding so there is no existing BEQ P%+5 ; explosion cloud to remove, so skip the following ; instruction JSR PTCLS ; Call PTCLS to remove the existing cloud by drawing it ; again LDA INWK+6 ; Set T = z_lo STA T LDA INWK+7 ; Set A = z_hi, so (A T) = z CMP #32 ; If z_hi < 32, skip the next two instructions BCC P%+6 LDA #$FE ; Set A = 254 and jump to yy (this BNE is effectively a BNE yy ; JMP, as A is never zero) ASL T ; Shift (A T) left twice ROL A ASL T ROL A SEC ; And then shift A left once more, inserting a 1 into ROL A ; bit 0 ; Overall, the above multiplies A by 8 and makes sure it ; is at least 1, to leave a one-byte distance in A. We ; can use this as the distance for our cloud, to ensure ; that the explosion cloud is visible even for ships ; that blow up a long way away .yy STA Q ; Store the distance to the explosion in Q LDY #1 ; Fetch byte #1 of the ship line heap, which contains LDA (XX19),Y ; the cloud counter STA frump ; Store the cloud counter in frump, so we can retrieve ; it below ADC #4 ; Add 4 to the cloud counter, so it ticks onwards every ; we redraw it BCS EX2 ; If the addition overflowed, jump up to EX2 to update ; the explosion flags and return from the subroutine STA (XX19),Y ; Store the updated cloud counter in byte #1 of the ship ; line heap JSR DVID4 ; Calculate the following: ; ; (P R) = 256 * A / Q ; = 256 * cloud counter / distance ; ; We are going to use this as our cloud size, so the ; further away the cloud, the smaller it is, and as the ; cloud counter ticks onward, the cloud expands LDA P ; Set A = P, so we now have: ; ; (A R) = 256 * cloud counter / distance CMP #$1C ; If A < 28, skip the next two instructions BCC P%+6 LDA #$FE ; Set A = 254 and skip the following (this BNE is BNE LABEL_1 ; effectively a JMP as A is never zero) ASL R ; Shift (A R) left three times to multiply by 8 ROL A ASL R ROL A ASL R ROL A ; Overall, the above multiplies (A R) by 8 to leave a ; one-byte cloud size in A, given by the following: ; ; A = 8 * cloud counter / distance .LABEL_1 DEY ; Decrement Y to 0 STA (XX19),Y ; Store the cloud size in byte #0 of the ship line heap LDA INWK+31 ; Clear bit 6 of the ship's byte #31 to denote that the AND #%10111111 ; explosion has not yet been drawn STA INWK+31 AND #%00001000 ; If bit 3 of the ship's byte #31 is clear, then nothing BEQ TT48 ; is being drawn on-screen for this ship anyway, so ; return from the subroutine (as TT48 contains an RTS) LDY #2 ; Otherwise it's time to draw an explosion cloud, so LDA (XX19),Y ; fetch byte #2 of the ship line heap into Y, which we TAY ; set to the explosion count for this ship (i.e. the ; number of vertices used as origins for explosion ; clouds) ; ; The explosion count is stored as 4 * n + 6, where n is ; the number of vertices, so the following loop copies ; the coordinates of the first n vertices from the heap ; at XX3, which is where we stored all the visible ; vertex coordinates in part 8 of the LL9 routine, and ; sticks them in the ship line heap pointed to by XX19, ; starting at byte #7 (so it leaves the first 6 bytes of ; the ship line heap alone) .EXL1 LDA XX3-7,Y ; Copy byte Y-7 from the XX3 heap, into the Y-th byte of STA (XX19),Y ; the ship line heap DEY ; Decrement the loop counter CPY #6 ; Keep copying vertex coordinates into the ship line BNE EXL1 ; heap until Y = 6 (which will copy n vertices, where n ; is the number of vertices we should be exploding) LDA INWK+31 ; Set bit 6 of the ship's byte #31 to denote that the ORA #%01000000 ; explosion has been drawn (as it's about to be) STA INWK+31 LDY frump ; Fetch the cloud counter into Y, which we stored in ; frump above CPY #18 ; If the cloud counter in Y is 18, then the explosion BNE P%+5 ; has just begun (as the cloud counter is initialised JMP PTCLS2S ; to 18 in part 1 of LL9), so jump to PTCLS2 via PTCLS2S ; to draw the explosion along with an explosion sprite, ; returning from the subroutine using a tail call .PTCLS ; This part of the routine actually draws the explosion ; cloud LDY #0 ; Fetch byte #0 of the ship line heap, which contains LDA (XX19),Y ; the cloud size we stored above, and store it in Q STA Q INY ; Increment the index in Y to point to byte #1 LDA (XX19),Y ; Fetch byte #1 of the ship line heap, which contains ; the cloud counter. We are now going to process this ; into the number of particles in each vertex's cloud BPL P%+4 ; If the cloud counter < 128, then we are in the first ; half of the cloud's existence, so skip the next ; instruction EOR #$FF ; Flip the value of A so that in the second half of the ; cloud's existence, A counts down instead of up LSR A ; Divide A by 16 so that is has a maximum value of 7 LSR A LSR A LSR A ORA #1 ; Make sure A is at least 1 and store it in U, to STA U ; give us the number of particles in the explosion for ; each vertex INY ; Increment the index in Y to point to byte #2 LDA (XX19),Y ; Fetch byte #2 of the ship line heap, which contains STA TGT ; the explosion count for this ship (i.e. the number of ; vertices used as origins for explosion clouds) and ; store it in TGT LDA RAND+1 ; Fetch the current random number seed in RAND+1 and PHA ; store it on the stack, so we can re-randomise the ; seeds when we are done LDY #6 ; Set Y = 6 to point to the byte before the first vertex ; coordinate we stored on the ship line heap above (we ; increment it below so it points to the first vertex) .EXL5 LDX #3 ; We are about to fetch a pair of coordinates from the ; ship line heap, so set a counter in X for 4 bytes .EXL3 INY ; Increment the index in Y so it points to the next byte ; from the coordinate we are copying LDA (XX19),Y ; Copy the Y-th byte from the ship line heap to the X-th STA K3,X ; byte of K3 DEX ; Decrement the X index BPL EXL3 ; Loop back to EXL3 until we have copied all four bytes ; The above loop copies the vertex coordinates from the ; ship line heap to K3, reversing them as we go, so it ; sets the following: ; ; K3+3 = x_lo ; K3+2 = x_hi ; K3+1 = y_lo ; K3+0 = y_hi STY CNT ; Set CNT to the index that points to the next vertex on ; the ship line heap LDY #2 ; Set Y = 2, which we will use to point to bytes #3 to ; #6, after incrementing it ; This next loop copies bytes #3 to #6 from the ship ; line heap into the four random number seeds in RAND to ; RAND+3, EOR'ing them with the vertex index so they are ; different for every vertex. This enables us to ; generate random numbers for drawing each vertex that ; are random but repeatable, which we need when we ; redraw the cloud to remove it ; ; Note that we haven't actually set the values of bytes ; #3 to #6 in the ship line heap, so we have no idea ; what they are, we just use what's already there. But ; the fact that those bytes are stored for this ship ; means we can repeat the random generation of the ; cloud, which is the important bit .EXL2 INY ; Increment the index in Y so it points to the next ; random number seed to copy LDA (XX19),Y ; Fetch the Y-th byte from the ship line heap EOR CNT ; EOR with the vertex index, so the seeds are different ; for each vertex STA $FFFF,Y ; Y is going from 3 to 6, so this stores the four bytes ; in memory locations $02, $03, $04 and $05, which are ; the memory locations of RAND through RAND+3 CPY #6 ; Loop back to EXL2 until Y = 6, which means we have BNE EXL2 ; copied four bytes LDY U ; Set Y to the number of particles in the explosion for ; each vertex, which we stored in U above. We will now ; use this as a loop counter to iterate through all the ; particles in the explosion .EXL4 CLC ; This contains the code from the DORND2 routine, so LDA RAND ; this section is exactly equivalent to a JSR DORND2 ROL A ; call, but is slightly faster as it's been inlined TAX ; (so it sets A and X to random values, making sure ADC RAND+2 ; the C flag doesn't affect the outcome) STA RAND STX RAND+2 LDA RAND+1 TAX ADC RAND+3 STA RAND+1 STX RAND+3 STA ZZ ; Set ZZ to a random number LDA K3+1 ; Set (A R) = (y_hi y_lo) STA R ; = y LDA K3 JSR EXS1 ; Set (A X) = (A R) +/- random * cloud size ; = y +/- random * cloud size BNE EX11 ; If A is non-zero, the particle is off-screen as the ; coordinate is either negative or bigger than 255, so ; jump to EX11 to do the next particle CPX #2*Y-1 ; If X > the y-coordinate of the bottom of the screen, BCS EX11 ; the particle is off the bottom of the screen, so jump ; to EX11 to do the next particle ; Otherwise X contains a random y-coordinate within the ; cloud STX Y1 ; Set Y1 = our random y-coordinate within the cloud LDA K3+3 ; Set (A R) = (x_hi x_lo) STA R LDA K3+2 JSR EXS1 ; Set (A X) = (A R) +/- random * cloud size ; = x +/- random * cloud size BNE EX4 ; If A is non-zero, the particle is off-screen as the ; coordinate is bigger than 255), so jump to EX4 to do ; the next particle ; Otherwise X contains a random x-coordinate within the ; cloud LDA Y1 ; Set A = our random y-coordinate within the cloud JSR PIXEL ; Draw a point at screen coordinate (X, A) with the ; point size determined by the distance in ZZ .EX4 DEY ; Decrement the loop counter for the next particle BPL EXL4 ; Loop back to EXL4 until we have done all the particles ; in the cloud LDY CNT ; Set Y to the index that points to the next vertex on ; the ship line heap CPY TGT ; If Y < TGT, which we set to the explosion count for BCC EXL5 ; this ship (i.e. the number of vertices used as origins ; for explosion clouds), loop back to EXL5 to do a cloud ; for the next vertex PLA ; Restore the current random number seed to RAND+1 that STA RAND+1 ; we stored at the start of the routine LDA K%+6 ; Store the z_lo coordinate for the planet (which will STA RAND+3 ; be pretty random) in the RAND+3 seed RTS ; Return from the subroutine .PTCLS2S JMP PTCLS2 ; Jump to PTCLS2 to display the explosion sprite (this ; JMP enables us to jump to PTCLS using a branch to ; PTCLS2S though this isn't actually done anywhere) .EX11 CLC ; This contains the code from the DORND2 routine, so LDA RAND ; this section is exactly equivalent to a JSR DORND2 ROL A ; call, but is slightly faster as it's been inlined TAX ; (so it sets A and X to random values, making sure ADC RAND+2 ; the C flag doesn't affect the outcome) STA RAND STX RAND+2 LDA RAND+1 TAX ADC RAND+3 STA RAND+1 STX RAND+3 JMP EX4 ; We just skipped a particle, so jump up to EX4 to do ; the next one .EXS1 ; This routine calculates the following: ; ; (A X) = (A R) +/- random * cloud size ; ; returning with the flags set for the high byte in A STA S ; Store A in S so we can use it later CLC ; This contains the code from the DORND2 routine, so LDA RAND ; this section is exactly equivalent to a JSR DORND2 ROL A ; call, but is slightly faster as it's been inlined TAX ; (so it sets A and X to random values, making sure ADC RAND+2 ; the C flag doesn't affect the outcome) STA RAND STX RAND+2 LDA RAND+1 TAX ADC RAND+3 STA RAND+1 STX RAND+3 ROL A ; Set A = A * 2 BCS EX5 ; If bit 7 of A was set (50% chance), jump to EX5 JSR FMLTU ; Set A = A * Q / 256 ; = random << 1 * projected cloud size / 256 ADC R ; Set (A X) = (S R) + A TAX ; = (S R) + random * projected cloud size ; ; where S contains the argument A, starting with the low ; bytes LDA S ; And then the high bytes ADC #0 RTS ; Return from the subroutine .EX5 JSR FMLTU ; Set T = A * Q / 256 STA T ; = random << 1 * projected cloud size / 256 LDA R ; Set (A X) = (S R) - T SBC T ; TAX ; where S contains the argument A, starting with the low ; bytes LDA S ; And then the high bytes SBC #0 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: exlook ; Type: Variable ; Category: Drawing ships ; Summary: A table to shift X left by one place when X is 0 or 1 ; ; ****************************************************************************** .exlook EQUB %00 ; Looking up exlook,X will return X shifted left by one EQUB %10 ; place, where X is 0 or 1 ; ; This is used in the PTCLS2 routine to update the top ; bit of the 9-bit x-coordinate for the explosion sprite ; ****************************************************************************** ; ; Name: PTCLS2 ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw the explosion along with an explosion sprite ; Deep dive: Sprite usage in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; This routine is very similar to the PTCLS section of the DOEXP subroutine, ; except it draws an explosion sprite along with the explosion cloud. It is only ; called once for each explosion cloud, at the start of the explosion process. ; ; ****************************************************************************** .PTCLS2 LDA #%101 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 1 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on ; ; See the memory map at the top of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore ; We now set up sprite 1, so we can use it to show the ; explosion burst as a colourful sprite (along with the ; usual cloud of explosion particles) LDA INWK+7 ; If z_hi >= 7 then set the following: CMP #7 ; LDA #%11111101 ; * A = %11111101 LDX #44 ; * X = 44 LDY #40 ; * Y = 40 BCS noexpand ; LDA #%11111111 ; otherwise set the following: LDX #32 ; LDY #30 ; * A = %11111111 ; * X = 32 ; * Y = 30 .noexpand STA VIC+$17 ; Store A in VIC registers $17 and $1D, which determine STA VIC+$1D ; whether sprites are double height and double width ; ; So this set sprite 1 to be standard width and height ; when z_hi >= 7 (as bit 1 of A is clear), or double ; width and height when z_hi < 7 (as bit 1 of A is set) ; ; We are about to use sprite 1 to display the explosion ; sprite, so this means that explosions that are far ; away will show a smaller sprite than explosions that ; are closer STX sprx ; Store X and Y in sprx and spry, to be used as the STY spry ; x- and y-coordinate offsets for the explosion sprite ; (i.e. the relative position of the sprite compared to ; the centre of the explosion, which needs to be set ; according to the size of the sprite) ; This part of the routine actually draws the explosion ; cloud LDY #0 ; Fetch byte #0 of the ship line heap, which contains LDA (XX19),Y ; the cloud size we stored above, and store it in Q STA Q INY ; Increment the index in Y to point to byte #1 LDA (XX19),Y ; Fetch byte #1 of the ship line heap, which contains ; the cloud counter. We are now going to process this ; into the number of particles in each vertex's cloud BPL P%+4 ; If the cloud counter < 128, then we are in the first ; half of the cloud's existence, so skip the next ; instruction EOR #$FF ; Flip the value of A so that in the second half of the ; cloud's existence, A counts down instead of up LSR A ; Divide A by 16 so that is has a maximum value of 7 LSR A LSR A LSR A ORA #1 ; Make sure A is at least 1 and store it in U, to STA U ; give us the number of particles in the explosion for ; each vertex INY ; Increment the index in Y to point to byte #2 LDA (XX19),Y ; Fetch byte #2 of the ship line heap, which contains STA TGT ; the explosion count for this ship (i.e. the number of ; vertices used as origins for explosion clouds) and ; store it in TGT LDA RAND+1 ; Fetch the current random number seed in RAND+1 and PHA ; store it on the stack, so we can re-randomise the ; seeds when we are done LDY #6 ; Set Y = 6 to point to the byte before the first vertex ; coordinate we stored on the ship line heap above (we ; increment it below so it points to the first vertex) .EXL52 LDX #3 ; We are about to fetch a pair of coordinates from the ; ship line heap, so set a counter in X for 4 bytes .EXL32 INY ; Increment the index in Y so it points to the next byte ; from the coordinate we are copying LDA (XX19),Y ; Copy the Y-th byte from the ship line heap to the X-th STA K3,X ; byte of K3 DEX ; Decrement the X index BPL EXL32 ; Loop back to EXL32 until we have copied all four bytes ; The above loop copies the vertex coordinates from the ; ship line heap to K3, reversing them as we go, so it ; sets the following: ; ; K3+3 = x_lo ; K3+2 = x_hi ; K3+1 = y_lo ; K3+0 = y_hi STY CNT ; Set CNT to the index that points to the next vertex on ; the ship line heap ; We now draw sprite 1, so it shows the explosion burst ; as a colourful sprite (along with the usual cloud of ; explosion particles) LDA K3+3 ; Set (A SC) = K3(2 3) + (0 sprx) CLC ; = (x_hi x_lo) + x-coordinate offset ADC sprx STA SC LDA K3+2 ADC #0 BMI yonk ; If the high byte of the result in (A SC) is negative, ; then the explosion is off the left edge of the screen, ; so jump to yonk to skip displaying the sprite CMP #2 ; If A >= 2 then (A SC) >= $200 (i.e. 512), so the BCS yonk ; explosion is way past the right edge of the screen, so ; jump to yonk to skip displaying the sprite TAX ; Set (X SC) = (A SC) ; ; so (X SC) contains the 9-bit x-coordinate of the ; explosion LDA K3+1 ; Set (A Y) = K3(0 1) + (0 spry) CLC ; = (y_hi y_lo) + y-coordinate offset ADC spry TAY LDA K3 ADC #0 BNE yonk ; If the high byte of the result in (A Y) is non-zero, ; then the explosion is either off the top of bottom ; edge of the screen, so jump to yonk to skip displaying ; the sprite CPY #2*Y+50 ; If the low byte of the result in (A Y) is greater than BCS yonk ; the height of the space view (2 * #Y) plus 50, then ; explosion is behind the dashboard (as it is more than ; 50 pixels below the top edge of the dashboard), so ; jump to yonk to skip displaying the sprite ; If we get here then the explosion is visible in the ; space view, so we now draw sprite 1, which contains ; the explosion sprite LDA VIC+$10 ; The 9-bit x-coordinate of the sprite is in (X SC), so AND #%11111101 ; set bit 1 of VIC register $10 when X is 1, or clear ORA exlook,X ; bit 1 of VIC register $10 when X is 1 (as VIC register STA VIC+$10 ; $10 contains the top bit of the x-coordinate for all ; eight sprites, with sprite 1's bit appearing in bit 1) ; ; The ORA exlook,X instruction is a neat way of setting ; bit 1 of A to the value of X, as exlook contains %00 ; and %10 (you could achieve the same effect by shifting ; X to the left by one place and OR'ing that, but that ; would require quite a bit of register shuffling on the ; 6502) LDX SC ; Set VIC registers $02 and $03 to the x-coordinate and STY VIC+$3 ; y-coordinate of sprite 1, which are in X and Y STX VIC+$2 LDA VIC+$15 ; Set bit 1 of VIC register $15 to enable sprite 1 so ORA #%00000010 ; the explosion sprite appears on-screen in the correct STA VIC+$15 ; position .yonk LDY #2 ; Set Y = 2, which we will use to point to bytes #3 to ; #6, after incrementing it ; This next loop copies bytes #3 to #6 from the ship ; line heap into the four random number seeds in RAND to ; RAND+3, EOR'ing them with the vertex index so they are ; different for every vertex. This enables us to ; generate random numbers for drawing each vertex that ; are random but repeatable, which we need when we ; redraw the cloud to remove it ; ; Note that we haven't actually set the values of bytes ; #3 to #6 in the ship line heap, so we have no idea ; what they are, we just use what's already there. But ; the fact that those bytes are stored for this ship ; means we can repeat the random generation of the ; cloud, which is the important bit .EXL22 INY ; Increment the index in Y so it points to the next ; random number seed to copy LDA (XX19),Y ; Fetch the Y-th byte from the ship line heap EOR CNT ; EOR with the vertex index, so the seeds are different ; for each vertex STA $FFFF,Y ; Y is going from 3 to 6, so this stores the four bytes ; in memory locations $02, $03, $04 and $05, which are ; the memory locations of RAND through RAND+3 CPY #6 ; Loop back to EXL22 until Y = 6, which means we have BNE EXL22 ; copied four bytes LDY U ; Set Y to the number of particles in the explosion for ; each vertex, which we stored in U above. We will now ; use this as a loop counter to iterate through all the ; particles in the explosion .EXL42 JSR DORND2 ; Set ZZ to a random number, making sure the C flag STA ZZ ; doesn't affect the outcome LDA K3+1 ; Set (A R) = (y_hi y_lo) STA R ; = y LDA K3 JSR EXS1 ; Set (A X) = (A R) +/- random * cloud size ; = y +/- random * cloud size BNE EX112 ; If A is non-zero, the particle is off-screen as the ; coordinate is bigger than 255), so jump to EX112 to do ; the next particle CPX #2*Y-1 ; If X > the y-coordinate of the bottom of the screen, BCS EX112 ; the particle is off the bottom of the screen, so jump ; to EX112 to do the next particle ; Otherwise X contains a random y-coordinate within the ; cloud STX Y1 ; Set Y1 = our random y-coordinate within the cloud LDA K3+3 ; Set (A R) = (x_hi x_lo) STA R LDA K3+2 JSR EXS1 ; Set (A X) = (A R) +/- random * cloud size ; = x +/- random * cloud size BNE EX42 ; If A is non-zero, the particle is off-screen as the ; coordinate is bigger than 255), so jump to EX42 to do ; the next particle LDA Y1 ; Set A = our random y-coordinate within the cloud JSR PIXEL ; Draw a point at screen coordinate (X, A) with the ; point size determined by the distance in ZZ .EX42 DEY ; Decrement the loop counter for the next particle BPL EXL42 ; Loop back to EXL42 until we have done all the ; particles in the cloud LDY CNT ; Set Y to the index that points to the next vertex on ; the ship line heap CPY TGT ; If Y < TGT, which we set to the explosion count for BCS P%+5 ; this ship (i.e. the number of vertices used as origins JMP EXL52 ; for explosion clouds), loop back to EXL52 to do a ; cloud for the next vertex PLA ; Restore the current random number seed to RAND+1 that STA RAND+1 ; we stored at the start of the routine LDA #%100 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore LDA K%+6 ; Store the z_lo coordinate for the planet (which will STA RAND+3 ; be pretty random) in the RAND+3 seed RTS ; Return from the subroutine .EX112 JSR DORND2 ; Set A and X to random numbers, making sure the C flag ; doesn't affect the outcome JMP EX42 ; We just skipped a particle, so jump up to EX42 to do ; the next one ; ****************************************************************************** ; ; Name: SOS1 ; Type: Subroutine ; Category: Universe ; Summary: Update the missile indicators, set up the planet data block ; ; ------------------------------------------------------------------------------ ; ; Update the missile indicators, and set up a data block for the planet, but ; only setting the pitch and roll counters to 127 (no damping). ; ; ****************************************************************************** .SOS1 JSR msblob ; Reset the dashboard's missile indicators so none of ; them are targeted LDA #127 ; Set the pitch and roll counters to 127, so that's a STA INWK+29 ; clockwise roll and a diving pitch with no damping, so STA INWK+30 ; the planet's rotation doesn't slow down LDA tek ; Set A = 128 or 130 depending on bit 1 of the system's AND #%00000010 ; tech level in tek ORA #%10000000 JMP NWSHP ; Add a new planet to our local bubble of universe, ; with the planet type defined by A (128 is a planet ; with an equator and meridian, 130 is a planet with ; a crater) ; ****************************************************************************** ; ; Name: SOLAR ; Type: Subroutine ; Category: Universe ; Summary: Set up various aspects of arriving in a new system ; Deep dive: A sense of scale ; The Trumbles mission ; ; ------------------------------------------------------------------------------ ; ; Halve our legal status, update the missile indicators, and set up data blocks ; and slots for the planet and sun. ; ; ****************************************************************************** .SOLAR LDA TRIBBLE ; If we have no Trumbles in the hold, skip to nobirths BEQ nobirths ; If we get here then we have Trumbles in the hold, so ; this is where they breed (though we never get here in ; the Master version as the number of Trumbles is always ; zero) LDA #0 ; Trumbles eat food and narcotics during the hyperspace STA QQ20 ; journey, so zero the amount of food and narcotics in STA QQ20+6 ; the hold JSR DORND ; Take the number of Trumbles from TRIBBLE(1 0), add a AND #15 ; random number between 4 and 15, and double the result, ADC TRIBBLE ; storing the resulting number in TRIBBLE(1 0) ORA #4 ; ROL A ; We start with the low byte STA TRIBBLE ROL TRIBBLE+1 ; And then do the high byte BPL P%+5 ; If bit 7 of the high byte is set, then rotate the high ROR TRIBBLE+1 ; byte back to the right, so the number of Trumbles is ; always positive .nobirths LSR FIST ; Halve our legal status in FIST, making us less bad, ; and moving bit 0 into the C flag (so every time we ; arrive in a new system, our legal status improves a ; bit) JSR ZINF ; Call ZINF to reset the INWK ship workspace, which ; doesn't affect the C flag LDA QQ15+1 ; Fetch s0_hi AND #%00000011 ; Extract bits 0-1 (which also help to determine the ; economy), which will be between 0 and 3 ADC #3 ; Add 3 + C, to get a result between 3 and 7, clearing ; the C flag in the process STA INWK+8 ; Store the result in z_sign in byte #6 ROR A ; Halve A, rotating in the C flag (which is clear) and STA INWK+2 ; store in both x_sign and y_sign, moving the planet to STA INWK+5 ; the upper right JSR SOS1 ; Call SOS1 to set up the planet's data block and add it ; to FRIN, where it will get put in the first slot as ; it's the first one to be added to our local bubble of ; this new system's universe LDA QQ15+3 ; Fetch s1_hi, extract bits 0-2, set bits 0 and 7 and AND #%00000111 ; store in z_sign, so the sun is behind us at a distance ORA #%10000001 ; of 1 to 7 STA INWK+8 LDA QQ15+5 ; Fetch s2_hi, extract bits 0-1 and store in x_sign and AND #%00000011 ; y_sign, so the sun is either dead centre in our rear STA INWK+2 ; laser crosshairs, or off to the top left by a distance STA INWK+1 ; of 1 or 2 when we look out the back LDA #0 ; Set the pitch and roll counters to 0 (no rotation) STA INWK+29 STA INWK+30 LDA #129 ; Set A = 129, the ship type for the sun JSR NWSHP ; Call NWSHP to set up the sun's data block and add it ; to FRIN, where it will get put in the second slot as ; it's the second one to be added to our local bubble ; of this new system's universe ; ****************************************************************************** ; ; Name: NWSTARS ; Type: Subroutine ; Category: Stardust ; Summary: Initialise the stardust field ; ; ------------------------------------------------------------------------------ ; ; This routine is called when the space view is initialised in routine LOOK1. ; ; ****************************************************************************** .NWSTARS LDA QQ11 ; If this is not a space view, jump to WPSHPS to skip ;ORA MJ ; the initialisation of the SX, SY and SZ tables. The OR BNE WPSHPS ; instruction is commented out in the original source, ; but it would have the effect of also skipping the ; initialisation if we had mis-jumped into witchspace ; ****************************************************************************** ; ; Name: nWq ; Type: Subroutine ; Category: Stardust ; Summary: Create a random cloud of stardust ; ; ------------------------------------------------------------------------------ ; ; Create a random cloud of stardust containing the correct number of dust ; particles, i.e. NOSTM of them, which is 3 in witchspace and 18 (#NOST) in ; normal space. Also clears the scanner and initialises the LSO block. ; ; This is called by the DEATH routine when it displays our untimely demise. ; ; ****************************************************************************** .nWq LDY NOSTM ; Set Y to the current number of stardust particles, so ; we can use it as a counter through all the stardust .SAL4 JSR DORND ; Set A and X to random numbers ORA #8 ; Set A so that it's at least 8 STA SZ,Y ; Store A in the Y-th particle's z_hi coordinate at ; SZ+Y, so the particle appears in front of us STA ZZ ; Set ZZ to the particle's z_hi coordinate JSR DORND ; Set A and X to random numbers STA SX,Y ; Store A in the Y-th particle's x_hi coordinate at ; SX+Y, so the particle appears in front of us STA X1 ; Set X1 to the particle's x_hi coordinate JSR DORND ; Set A and X to random numbers STA SY,Y ; Store A in the Y-th particle's y_hi coordinate at ; SY+Y, so the particle appears in front of us STA Y1 ; Set Y1 to the particle's y_hi coordinate JSR PIXEL2 ; Draw a stardust particle at (X1,Y1) with distance ZZ DEY ; Decrement the counter to point to the next particle of ; stardust BNE SAL4 ; Loop back to SAL4 until we have randomised all the ; stardust particles ;JSR PBFL ; This instruction is commented out in the original ; source ; Fall through into WPSHPS to clear the scanner and ; reset the LSO block ; ****************************************************************************** ; ; Name: WPSHPS ; Type: Subroutine ; Category: Dashboard ; Summary: Clear the scanner, reset the ball line and sun line heaps ; ; ------------------------------------------------------------------------------ ; ; Remove all ships from the scanner, reset the sun line heap at LSO, and reset ; the ball line heap at LSX2 and LSY2. ; ; ****************************************************************************** .WPSHPS LDX #0 ; Set up a counter in X to work our way through all the ; ship slots in FRIN .WSL1 LDA FRIN,X ; Fetch the ship type in slot X BEQ WS2 ; If the slot contains 0 then it is empty and we have ; checked all the slots (as they are always shuffled ; down in the main loop to close up any gaps), so jump ; to WS2 as we are done BMI WS1 ; If the slot contains a ship type with bit 7 set, then ; it contains the planet or the sun, so jump down to WS1 ; to skip this slot, as the planet and sun don't appear ; on the scanner STA TYPE ; Store the ship type in TYPE JSR GINF ; Call GINF to get the address of the data block for ; ship slot X and store it in INF LDY #31 ; We now want to copy the first 32 bytes from the ship's ; data block into INWK, so set a counter in Y .WSL2 LDA (INF),Y ; Copy the Y-th byte from the data block pointed to by STA INWK,Y ; INF into the Y-th byte of INWK workspace DEY ; Decrement the counter to point at the next byte BPL WSL2 ; Loop back to WSL2 until we have copied all 32 bytes STX XSAV ; Store the ship slot number in XSAV while we call SCAN JSR SCAN ; Call SCAN to plot this ship on the scanner, which will ; remove it as it's plotted with EOR logic LDX XSAV ; Restore the ship slot number from XSAV into X LDY #31 ; Clear bits 3, 4 and 6 in the ship's byte #31, which LDA (INF),Y ; stops drawing the ship on-screen (bit 3), hides it AND #%10100111 ; from the scanner (bit 4) and stops any lasers firing STA (INF),Y ; (bit 6) .WS1 INX ; Increment X to point to the next ship slot BNE WSL1 ; Loop back up to process the next slot (this BNE is ; effectively a JMP as X will never be zero) .WS2 LDX #0 ; Reset the ball line heap by setting the ball line heap STX LSP ; pointer to 0 DEX ; Set X = $FF STX LSX2 ; Set LSX2 = LSY2 = $FF to clear the ball line heap STX LSY2 ; Fall through into FLFLLS to reset the LSO block ; ****************************************************************************** ; ; Name: FLFLLS ; Type: Subroutine ; Category: Drawing suns ; Summary: Reset the sun line heap ; ; ------------------------------------------------------------------------------ ; ; Reset the sun line heap at LSO by zero-filling it and setting the first byte ; to $FF. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A is set to 0 ; ; ****************************************************************************** .FLFLLS LDY #199 ; Set Y to the screen height of (which is 200 pixels) so ; we can draw suns on the entire screen (so we can draw ; systems on the Short-range Chart, which uses the ; entire screen height) LDA #0 ; Set A to 0 so we can zero-fill the LSO block .SAL6 STA LSO,Y ; Set the Y-th byte of the LSO block to 0 DEY ; Decrement the counter BNE SAL6 ; Loop back until we have filled all the way to LSO+1 DEY ; Decrement Y to value of $FF (as we exit the above loop ; with Y = 0) STY LSX ; Set the first byte of the LSO block, which has its own ; label LSX, to $FF, to indicate that the sun line heap ; is empty RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DET1 ; Type: Subroutine ; Category: Drawing the screen ; Summary: Show or hide the dashboard (for when we die) ; ; ------------------------------------------------------------------------------ ; ; This subroutine does nothing in the Commodore 64 and Apple II versions of ; Elite, as the dashboard is not hidden when we die. ; ; ****************************************************************************** .DET1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: SHD ; Type: Subroutine ; Category: Flight ; Summary: Charge a shield and drain some energy from the energy banks ; ; ------------------------------------------------------------------------------ ; ; Charge up a shield, and if it needs charging, drain some energy from the ; energy banks. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The value of the shield to recharge ; ; ****************************************************************************** DEX ; If we get here then we just incremented the shield ; value back around to zero, so decrement it back down ; to 255 so it stays at the maximum value of 255 RTS ; Return from the subroutine .SHD INX ; Increment the shield value BEQ SHD-2 ; If the shield value is 0 then this means it was 255 ; before, which is the maximum value, so jump to SHD-2 ; to bring it back down to 255 and return without ; draining our energy banks ; Otherwise fall through into DENGY to drain our energy ; to pay for all this shield charging ; ****************************************************************************** ; ; Name: DENGY ; Type: Subroutine ; Category: Flight ; Summary: Drain some energy from the energy banks ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Z flag Set if we have no energy left, clear otherwise ; ; ****************************************************************************** .DENGY DEC ENERGY ; Decrement the energy banks in ENERGY PHP ; Save the flags on the stack BNE P%+5 ; If the energy levels are not yet zero, skip the ; following instruction INC ENERGY ; The minimum allowed energy level is 1, and we just ; reached 0, so increment ENERGY back to 1 PLP ; Restore the flags from the stack, so we return with ; the Z flag from the DEC instruction above RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: COMPAS ; Type: Subroutine ; Category: Dashboard ; Summary: Update the compass ; ; ****************************************************************************** .COMPAS JSR DOT ; Call DOT to redraw (i.e. remove) the current compass ; dot LDA SSPR ; If we are inside the space station safe zone, jump to BNE SP1 ; SP1 to draw the space station on the compass JSR SPS1 ; Otherwise we need to draw the planet on the compass, ; so first call SPS1 to calculate the vector to the ; planet and store it in XX15 JMP SP2 ; Jump to SP2 to draw XX15 on the compass, returning ; from the subroutine using a tail call ; ****************************************************************************** ; ; Name: SPS2 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (Y X) = A / 10 ; ; ------------------------------------------------------------------------------ ; ; Calculate the following, where A is a sign-magnitude 8-bit integer and the ; result is a signed 16-bit integer: ; ; (Y X) = A / 10 ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag The C flag is cleared ; ; ****************************************************************************** .SPS2 ASL A ; Set X = |A| * 2, and set the C flag to the sign bit of TAX ; A LDA #0 ; Set Y to have the sign bit from A in bit 7, with the ROR A ; rest of its bits zeroed, so Y now contains the sign of TAY ; the original argument LDA #20 ; Set Q = 20 STA Q TXA ; Copy X into A, so A now contains the argument A * 2 JSR DVID4 ; Calculate the following: ; ; P = A / Q ; = |argument A| * 2 / 20 ; = |argument A| / 10 LDX P ; Set X to the result TYA ; If the sign of the original argument A is negative, BMI LL163 ; jump to LL163 to flip the sign of the result LDY #0 ; Set the high byte of the result to 0, as the result is ; positive RTS ; Return from the subroutine .LL163 LDY #$FF ; The result is negative, so set the high byte to $FF TXA ; Flip the low byte and add 1 to get the negated low EOR #$FF ; byte, using two's complement TAX INX RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: SPS4 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Calculate the vector to the space station ; ; ------------------------------------------------------------------------------ ; ; Calculate the vector between our ship and the space station and store it in ; XX15. ; ; ****************************************************************************** .SPS4 LDX #8 ; First we need to copy the space station's coordinates ; into K3, so set a counter to copy the first 9 bytes ; (the three-byte x, y and z coordinates) from the ; station's data block at K% + NI% into K3 .SPL1 LDA K%+NI%,X ; Copy the X-th byte from the station's data block at STA K3,X ; K% + NI% to the X-th byte of K3 DEX ; Decrement the loop counter BPL SPL1 ; Loop back to SPL1 until we have copied all 9 bytes JMP TAS2 ; Call TAS2 to build XX15 from K3, returning from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: SP1 ; Type: Subroutine ; Category: Dashboard ; Summary: Draw the space station on the compass ; ; ****************************************************************************** .SP1 JSR SPS4 ; Call SPS4 to calculate the vector to the space station ; and store it in XX15 ; Fall through into SP2 to draw XX15 on the compass ; ****************************************************************************** ; ; Name: SP2 ; Type: Subroutine ; Category: Dashboard ; Summary: Draw a dot on the compass, given the planet/station vector ; ; ------------------------------------------------------------------------------ ; ; Draw a dot on the compass to represent the planet or station, whose normalised ; vector is in XX15. ; ; XX15 to XX15+2 The normalised vector to the planet or space station, ; stored as x in XX15, y in XX15+1 and z in XX15+2 ; ; ****************************************************************************** .SP2 LDA XX15 ; Set A to the x-coordinate of the planet or station to ; show on the compass, which will be in the range -96 to ; +96 as the vector has been normalised JSR SPS2 ; Set (Y X) = A / 10, so X will be from -9 to +9, which ; is the x-offset from the centre of the compass of the ; dot we want to draw. Returns with the C flag clear TXA ; Set COMX = 195 + X, as 186 is the pixel x-coordinate ADC #195 ; of the leftmost dot possible on the compass, and X can STA COMX ; be -9, which would be 195 - 9 = 186. This also means ; that the highest value for COMX is 195 + 9 = 204, ; which is the pixel x-coordinate of the rightmost dot ; in the compass... but the compass dot is actually two ; pixels wide, so the compass dot can overlap the right ; edge of the compass, but not the left edge LDA XX15+1 ; Set A to the y-coordinate of the planet or station to ; show on the compass, which will be in the range -96 to ; +96 as the vector has been normalised JSR SPS2 ; Set (Y X) = A / 10, so X will be from -9 to +9, which ; is the x-offset from the centre of the compass of the ; dot we want to draw. Returns with the C flag clear STX T ; Set COMY = 156 - X, as 155 is the pixel y-coordinate LDA #156 ; of the centre of the compass, the C flag is clear, SBC T ; and the y-axis needs to be flipped around (because STA COMY ; when the planet or station is above us, and the ; vector is therefore positive, we want to show the dot ; higher up on the compass, which has a smaller pixel ; y-coordinate). So this calculation does this: ; ; COMY = 156 - X - (1 - 0) = 155 - X LDA #YELLOW ; Set A to yellow, the colour for when the planet or ; station in the compass is in front of us LDX XX15+2 ; If the z-coordinate of the XX15 vector is positive, BPL P%+4 ; skip the following instruction LDA #GREEN ; The z-coordinate of XX15 is negative, so the planet or ; station is behind us and the compass dot should be in ; green, so set A accordingly STA COMC ; Store the compass colour in COMC JMP DOT ; Jump to DOT to draw the dot on the compass and return ; from the subroutine using a tail call ; ****************************************************************************** ; ; Name: OOPS ; Type: Subroutine ; Category: Flight ; Summary: Take some damage ; ; ------------------------------------------------------------------------------ ; ; We just took some damage, so reduce the shields if we have any, or reduce the ; energy levels and potentially take some damage to the cargo if we don't. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The amount of damage to take ; ; INF The address of the ship block for the ship that attacked ; us, or the ship that we just ran into ; ; ****************************************************************************** .OOPS STA T ; Store the amount of damage in T LDX #0 ; Fetch byte #8 (z_sign) for the ship attacking us, and LDY #8 ; set X = 0 LDA (INF),Y BMI OO1 ; If A is negative, then we got hit in the rear, so jump ; to OO1 to process damage to the aft shield LDA FSH ; Otherwise the forward shield was damaged, so fetch the SBC T ; shield strength from FSH and subtract the damage in T BCC OO2 ; If the C flag is clear then this amount of damage was ; too much for the shields, so jump to OO2 to set the ; shield level to 0 and start taking damage directly ; from the energy banks STA FSH ; Store the new value of the forward shield in FSH RTS ; Return from the subroutine .OO2 LDX #0 ; Set the forward shield to 0 STX FSH BCC OO3 ; Jump to OO3 to start taking damage directly from the ; energy banks (this BCC is effectively a JMP as the C ; flag is clear, as we jumped to OO2 with a BCC) .OO1 LDA ASH ; The aft shield was damaged, so fetch the shield SBC T ; strength from ASH and subtract the damage in T BCC OO5 ; If the C flag is clear then this amount of damage was ; too much for the shields, so jump to OO5 to set the ; shield level to 0 and start taking damage directly ; from the energy banks STA ASH ; Store the new value of the aft shield in ASH RTS ; Return from the subroutine .OO5 LDX #0 ; Set the forward shield to 0 STX ASH .OO3 ADC ENERGY ; A is negative and contains the amount by which the STA ENERGY ; damage overwhelmed the shields, so this drains the ; energy banks by that amount (and because the energy ; banks are shown over four indicators rather than one, ; but with the same value range of 0-255, energy will ; appear to drain away four times faster than the ; shields did) BEQ P%+4 ; If we have just run out of energy, skip the next ; instruction to jump straight to our death BCS P%+5 ; If the C flag is set, then subtracting the damage from ; the energy banks didn't underflow, so we had enough ; energy to survive, and we can skip the next ; instruction to make a sound and take some damage JMP DEATH ; Otherwise our energy levels are either 0 or negative, ; and in either case that means we jump to our DEATH, ; returning from the subroutine using a tail call JSR EXNO3 ; We didn't die, so call EXNO3 to make the sound of a ; collision JMP OUCH ; And jump to OUCH to take damage and return from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: SPS3 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Copy a space coordinate from the K% block into K3 ; ; ------------------------------------------------------------------------------ ; ; Copy one of the planet's coordinates into the corresponding location in the ; temporary variable K3. The high byte and absolute value of the sign byte are ; copied into the first two K3 bytes, and the sign of the sign byte is copied ; into the highest K3 byte. ; ; The comments below are written for copying the planet's x-coordinate into ; K3(2 1 0). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X Determines which coordinate to copy, and to where: ; ; * X = 0 copies (x_sign, x_hi) into K3(2 1 0) ; ; * X = 3 copies (y_sign, y_hi) into K3(5 4 3) ; ; * X = 6 copies (z_sign, z_hi) into K3(8 7 6) ; ; ****************************************************************************** .SPS3 LDA K%+1,X ; Copy x_hi into K3+X STA K3,X LDA K%+2,X ; Set A = Y = x_sign TAY AND #%01111111 ; Set K3+1 = |x_sign| STA K3+1,X TYA ; Set K3+2 = the sign of x_sign AND #%10000000 STA K3+2,X RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: NWSPS ; Type: Subroutine ; Category: Universe ; Summary: Add a new space station to our local bubble of universe ; ; ****************************************************************************** .NWSPS JSR SPBLB ; Light up the space station bulb on the dashboard LDX #%10000001 ; Set the AI flag in byte #32 to %10000001 (AI enabled, STX INWK+32 ; has an E.C.M.) LDX #0 ; Set pitch counter to 0 (no pitch, roll only) STX INWK+30 STX NEWB ; Set NEWB to %00000000, though this gets overridden by ; the default flags from E% in NWSHP below ;STX INWK+31 ; This instruction is commented out in the original ; source. It would set the exploding state and missile ; count to 0 STX FRIN+1 ; Set the second slot in the FRIN table to 0, so when we ; fall through into NWSHP below, the new station that ; gets created will go into slot FRIN+1, as this will be ; the first empty slot that the routine finds DEX ; Set the roll counter to 255 (maximum anti-clockwise STX INWK+29 ; roll with no damping) LDX #10 ; Call NwS1 to flip the sign of nosev_x_hi (byte #10) JSR NwS1 JSR NwS1 ; And again to flip the sign of nosev_y_hi (byte #12) JSR NwS1 ; And again to flip the sign of nosev_z_hi (byte #14) LDA spasto ; Copy the address of the Coriolis space station's ship STA XX21+2*SST-2 ; blueprint from spasto to the #SST entry in the LDA spasto+1 ; blueprint lookup table at XX21, so when we spawn a STA XX21+2*SST-1 ; ship of type #SST, it will be a Coriolis station LDA tek ; If the system's tech level in tek is less than 10, CMP #10 ; jump to notadodo, so tech levels 0 to 9 have Coriolis BCC notadodo ; stations, while 10 and above will have Dodo stations LDA XX21+2*DOD-2 ; Copy the address of the Dodo space station's ship STA XX21+2*SST-2 ; blueprint from spasto to the #SST entry in the LDA XX21+2*DOD-1 ; blueprint lookup table at XX21, so when we spawn a STA XX21+2*SST-1 ; ship of type #SST, it will be a Dodo station .notadodo LDA #LO(LSO) ; Set bytes #33 and #34 to point to LSO for the ship STA INWK+33 ; line heap for the space station LDA #HI(LSO) STA INWK+34 LDA #SST ; Set A to the space station type, and fall through ; into NWSHP to finish adding the space station to the ; universe ; ****************************************************************************** ; ; Name: NWSHP ; Type: Subroutine ; Category: Universe ; Summary: Add a new ship to our local bubble of universe ; ; ------------------------------------------------------------------------------ ; ; This creates a new block of ship data in the K% workspace, allocates a new ; block in the ship line heap at WP, adds the new ship's type into the first ; empty slot in FRIN, and adds a pointer to the ship data into UNIV. If there ; isn't enough free memory for the new ship, it isn't added. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The type of the ship to add (see variable XX21 for a ; list of ship types) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if the ship was successfully added, clear if it ; wasn't (as there wasn't enough free memory) ; ; INF Points to the new ship's data block in K% ; ; ****************************************************************************** .NWSHP STA T ; Store the ship type in location T LDX #0 ; Before we can add a new ship, we need to check ; whether we have an empty slot we can put it in. To do ; this, we need to loop through all the slots to look ; for an empty one, so set a counter in X that starts ; from the first slot at 0. When ships are killed, then ; the slots are shuffled down by the KILLSHP routine, so ; the first empty slot will always come after the last ; filled slot. This allows us to tack the new ship's ; data block and ship line heap onto the end of the ; existing ship data and heap, as shown in the memory ; map below .NWL1 LDA FRIN,X ; Load the ship type for the X-th slot BEQ NW1 ; If it is zero, then this slot is empty and we can use ; it for our new ship, so jump down to NW1 INX ; Otherwise increment X to point to the next slot CPX #NOSH ; If we haven't reached the last slot yet, loop back up BCC NWL1 ; to NWL1 to check the next slot (note that this means ; only slots from 0 to #NOSH - 1 are populated by this ; routine, but there is one more slot reserved in FRIN, ; which is used to identify the end of the slot list ; when shuffling the slots down in the KILLSHP routine) .NW3 CLC ; Otherwise we don't have an empty slot, so we can't RTS ; add a new ship, so clear the C flag to indicate that ; we have not managed to create the new ship, and return ; from the subroutine .NW1 ; If we get here, then we have found an empty slot at ; index X, so we can go ahead and create our new ship. ; We do that by creating a ship data block at INWK and, ; when we are done, copying the block from INWK into ; the K% workspace (specifically, to INF) JSR GINF ; Get the address of the data block for ship slot X ; (which is in workspace K%) and store it in INF LDA T ; If the type of ship that we want to create is BMI NW2 ; negative, then this indicates a planet or sun, so ; jump down to NW2, as the next section sets up a ship ; data block, which doesn't apply to planets and suns, ; as they don't have things like shields, missiles, ; vertices and edges ; This is a ship, so first we need to set up various ; pointers to the ship blueprint we will need. The ; blueprints for each ship type in Elite are stored ; in a table at location XX21, so refer to the comments ; on that variable for more details on the data we're ; about to access ASL A ; Set Y = ship type * 2 TAY LDA XX21-1,Y ; The ship blueprints at XX21 start with a lookup ; table that points to the individual ship blueprints, ; so this fetches the high byte of this particular ship ; type's blueprint BEQ NW3 ; If the high byte is 0 then this is not a valid ship ; type, so jump to NW3 to clear the C flag and return ; from the subroutine STA XX0+1 ; This is a valid ship type, so store the high byte in ; XX0+1 LDA XX21-2,Y ; Fetch the low byte of this particular ship type's STA XX0 ; blueprint and store it in XX0, so XX0(1 0) now ; contains the address of this ship's blueprint CPY #2*SST ; If the ship type is a space station (SST), then jump BEQ NW6 ; to NW6, skipping the heap space steps below, as the ; space station has its own line heap at LSO (which it ; shares with the sun) ; We now want to allocate space for a heap that we can ; use to store the lines we draw for our new ship (so it ; can easily be erased from the screen again). SLSP ; points to the start of the current heap space, and we ; can extend it downwards with the heap for our new ship ; (as the heap space always ends just before the WP ; workspace) LDY #5 ; Fetch ship blueprint byte #5, which contains the LDA (XX0),Y ; maximum heap size required for plotting the new ship, STA T1 ; and store it in T1 LDA SLSP ; Take the 16-bit address in SLSP and subtract T1, SEC ; storing the 16-bit result in INWK(34 33), so this now SBC T1 ; points to the start of the line heap for our new ship STA INWK+33 LDA SLSP+1 SBC #0 STA INWK+34 ; We now need to check that there is enough free space ; for both this new line heap and the new data block ; for our ship. In memory, this is the layout of the ; ship data blocks and ship line heaps: ; ; +-----------------------------------+ $FFC0 = LS% ; | | ; | Current ship line heap | ; | | ; +-----------------------------------+ SLSP ; | | ; | Proposed heap for new ship | ; | | ; +-----------------------------------+ INWK(34 33) ; | | ; . . ; . . ; . . ; . . ; . . ; | | ; +-----------------------------------+ INF + NI% ; | | ; | Proposed data block for new ship | ; | | ; +-----------------------------------+ INF ; | | ; | Existing ship data blocks | ; | | ; +-----------------------------------+ $F900 = K% ; ; So, to work out if we have enough space, we have to ; make sure there is room between the end of our new ; ship data block at INF + NI%, and the start of the ; proposed heap for our new ship at the address we ; stored in INWK(34 33). Or, to put it another way, we ; and to make sure that: ; ; INWK(34 33) > INF + NI% ; ; which is the same as saying: ; ; INWK+33 - INF > NI% ; ; because INWK is in zero page, so INWK+34 = 0 LDA INWK+33 ; Calculate INWK+33 - INF, again using 16-bit ;SEC ; arithmetic, and put the result in (A Y), so the high SBC INF ; byte is in A and the low byte in Y. The SEC TAY ; instruction is commented out in the original source; LDA INWK+34 ; as the previous subtraction will never underflow, it SBC INF+1 ; is superfluous BCC NW3+1 ; If we have an underflow from the subtraction, then ; INF > INWK+33 and we definitely don't have enough ; room for this ship, so jump to NW3+1, which returns ; from the subroutine (with the C flag already cleared) BNE NW4 ; If the subtraction of the high bytes in A is not ; zero, and we don't have underflow, then we definitely ; have enough space, so jump to NW4 to continue setting ; up the new ship CPY #NI% ; Otherwise the high bytes are the same in our BCC NW3+1 ; subtraction, so now we compare the low byte of the ; result (which is in Y) with NI%. This is the same as ; doing INWK+33 - INF > NI% (see above). If this isn't ; true, the C flag will be clear and we don't have ; enough space, so we jump to NW3+1, which returns ; from the subroutine (with the C flag already cleared) .NW4 LDA INWK+33 ; If we get here then we do have enough space for our STA SLSP ; new ship, so store the new bottom of the ship line LDA INWK+34 ; heap (i.e. INWK+33) in SLSP, doing both the high and STA SLSP+1 ; low bytes .NW6 LDY #14 ; Fetch ship blueprint byte #14, which contains the LDA (XX0),Y ; ship's energy, and store it in byte #35 STA INWK+35 LDY #19 ; Fetch ship blueprint byte #19, which contains the LDA (XX0),Y ; number of missiles and laser power, and AND with %111 AND #%00000111 ; to extract the number of missiles before storing in STA INWK+31 ; byte #31 LDA T ; Restore the ship type we stored above .NW2 STA FRIN,X ; Store the ship type in the X-th byte of FRIN, so the ; slot is now shown as occupied in the index table TAX ; Copy the ship type into X BMI NW8 ; If the ship type is negative (planet or sun), then ; jump to NW8 to skip the following instructions CPX #HER ; If the ship type is a rock hermit, jump to gangbang BEQ gangbang ; to increase the junk count CPX #JL ; If JL <= X < JH, i.e. the type of ship we added in X BCC NW7 ; is junk (escape pod, alloy plate, cargo canister, CPX #JH ; asteroid, splinter, Shuttle or Transporter), then keep BCS NW7 ; going, otherwise jump to NW7 .gangbang INC JUNK ; We're adding junk, so increase the junk counter .NW7 INC MANY,X ; Increment the total number of ships of type X .NW8 LDY T ; Restore the ship type we stored above LDA E%-1,Y ; Fetch the E% byte for this ship to get the default ; settings for the ship's NEWB flags AND #%01101111 ; Zero bits 4 and 7 (so the new ship is not docking, has ; not been scooped, and has not just docked) ORA NEWB ; Apply the result to the ship's NEWB flags, which sets STA NEWB ; bits 0-3 and 5-6 in NEWB if they are set in the E% ; byte LDY #NI%-1 ; The final step is to copy the new ship's data block ; from INWK to INF, so set up a counter for NI% bytes ; in Y .NWL3 LDA INWK,Y ; Load the Y-th byte of INWK and store in the Y-th byte STA (INF),Y ; of the workspace pointed to by INF DEY ; Decrement the loop counter BPL NWL3 ; Loop back for the next byte until we have copied them ; all over SEC ; We have successfully created our new ship, so set the ; C flag to indicate success RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: NwS1 ; Type: Subroutine ; Category: Universe ; Summary: Flip the sign and double an INWK byte ; ; ------------------------------------------------------------------------------ ; ; Flip the sign of the INWK byte at offset X, and increment X by 2. This is ; used by the space station creation routine at NWSPS. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The offset of the INWK byte to be flipped ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; X X is incremented by 2 ; ; ****************************************************************************** .NwS1 LDA INWK,X ; Load the X-th byte of INWK into A and flip bit 7, EOR #%10000000 ; storing the result back in the X-th byte of INWK STA INWK,X INX ; Add 2 to X INX RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: ABORT ; Type: Subroutine ; Category: Dashboard ; Summary: Unarm missiles and update the dashboard indicators ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The new colour of the missile indicator: ; ; * $00 = black (no missile) ; ; * #RED2 = red (armed and locked) ; ; * #YELLOW2 = yellow/white (armed) ; ; * #GREEN2 = green (unarmed) ; ; ****************************************************************************** .ABORT LDX #$FF ; Set X to $FF, which is the value of MSTG when we have ; no target lock for our missile ; Fall through into ABORT2 to set the missile lock to ; the value in X, which effectively unarms the missile ; ****************************************************************************** ; ; Name: ABORT2 ; Type: Subroutine ; Category: Dashboard ; Summary: Set/unset the lock target for a missile and update the dashboard ; ; ------------------------------------------------------------------------------ ; ; Set the lock target for the leftmost missile and update the dashboard. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The slot number of the ship to lock our missile onto, or ; $FF to remove missile lock ; ; Y The new colour of the missile indicator: ; ; * $00 = black (no missile) ; ; * #RED2 = red (armed and locked) ; ; * #YELLOW2 = yellow/white (armed) ; ; * #GREEN2 = green (unarmed) ; ; ****************************************************************************** .ABORT2 STX MSTG ; Store the target of our missile lock in MSTG LDX NOMSL ; Call MSBAR to update the leftmost indicator in the JSR MSBAR ; dashboard's missile bar, which returns with Y = 0 STY MSAR ; Set MSAR = 0 to indicate that the leftmost missile ; is no longer seeking a target lock RTS ; Return from the subroutine .msbpars EQUB 4, 0, 0, 0, 0 ; These bytes appear to be unused (they are left over ; from the 6502 Second Processor version of Elite) ; ****************************************************************************** ; ; Name: PROJ ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Project the current ship or planet onto the screen ; Deep dive: Extended screen coordinates ; ; ------------------------------------------------------------------------------ ; ; Project the current ship's location or the planet onto the screen, either ; returning the screen coordinates of the projection (if it's on-screen), or ; returning an error via the C flag. ; ; In this context, "on-screen" means that the point is projected into the ; following range: ; ; centre of screen - 1024 < x < centre of screen + 1024 ; centre of screen - 1024 < y < centre of screen + 1024 ; ; This is to cater for ships (and, more likely, planets and suns) whose centres ; are off-screen but whose edges may still be visible. ; ; The projection calculation is: ; ; K3(1 0) = #X + x / z ; K4(1 0) = #Y + y / z ; ; where #X and #Y are the pixel x-coordinate and y-coordinate of the centre of ; the screen. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; INWK The ship data block for the ship to project on-screen ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; K3(1 0) The x-coordinate of the ship's projection on-screen ; ; K4(1 0) The y-coordinate of the ship's projection on-screen ; ; C flag Set if the ship's projection doesn't fit on the screen, ; clear if it does project onto the screen ; ; A Contains K4+1, the high byte of the y-coordinate ; ; ****************************************************************************** .PROJ LDA INWK ; Set P(1 0) = (x_hi x_lo) STA P ; = x LDA INWK+1 STA P+1 LDA INWK+2 ; Set A = x_sign JSR PLS6 ; Call PLS6 to calculate: ; ; (X K) = (A P+1 P) / (z_sign z_hi z_lo) ; = (x_sign x_hi x_lo) / (z_sign z_hi z_lo) ; = x / z BCS PL2-1 ; If the C flag is set then the result overflowed and ; the coordinate doesn't fit on the screen, so return ; from the subroutine with the C flag set (as PL2-1 ; contains an RTS) LDA K ; Set K3(1 0) = (X K) + #X ADC #X ; = #X + x / z STA K3 ; ; first doing the low bytes TXA ; And then the high bytes. #X is the x-coordinate of ADC #0 ; the centre of the space view, so this converts the STA K3+1 ; space x-coordinate into a screen x-coordinate LDA INWK+3 ; Set P(1 0) = (y_hi y_lo) STA P LDA INWK+4 STA P+1 LDA INWK+5 ; Set A = -y_sign EOR #%10000000 JSR PLS6 ; Call PLS6 to calculate: ; ; (X K) = (A P+1 P) / (z_sign z_hi z_lo) ; = -(y_sign y_hi y_lo) / (z_sign z_hi z_lo) ; = -y / z BCS PL2-1 ; If the C flag is set then the result overflowed and ; the coordinate doesn't fit on the screen, so return ; from the subroutine with the C flag set (as PL2-1 ; contains an RTS) LDA K ; Set K4(1 0) = (X K) + #Y ADC #Y ; = #Y - y / z STA K4 ; ; first doing the low bytes TXA ; And then the high bytes. #Y is the y-coordinate of ADC #0 ; the centre of the space view, so this converts the STA K4+1 ; space y-coordinate into a screen y-coordinate CLC ; Clear the C flag to indicate success RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: PL2 ; Type: Subroutine ; Category: Drawing planets ; Summary: Remove the planet or sun from the screen ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; PL2-1 Contains an RTS ; ; ****************************************************************************** .PL2 LDA TYPE ; Shift bit 0 of the planet/sun's type into the C flag LSR A BCS P%+5 ; If the planet/sun's type has bit 0 clear, then it's ; either 128 or 130, which is a planet; meanwhile, the ; sun has type 129, which has bit 0 set. So if this is ; the sun, skip the following instruction JMP WPLS2 ; This is the planet, so jump to WPLS2 to remove it from ; screen, returning from the subroutine using a tail ; call JMP WPLS ; This is the sun, so jump to WPLS to remove it from ; screen, returning from the subroutine using a tail ; call ; ****************************************************************************** ; ; Name: PLANET ; Type: Subroutine ; Category: Drawing planets ; Summary: Draw the planet or sun ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; INWK The planet or sun's ship data block ; ; ****************************************************************************** .PLANET LDA INWK+8 ; Set A = z_sign (the highest byte in the planet/sun's ; coordinates) ;BMI PL2 ; This instruction is commented out in the original ; source. It would remove the planet from the screen ; when it's behind us CMP #48 ; If A >= 48 then the planet/sun is too far away to be BCS PL2 ; seen, so jump to PL2 to remove it from the screen, ; returning from the subroutine using a tail call ORA INWK+7 ; Set A to 0 if both z_sign and z_hi are 0 BEQ PL2 ; If both z_sign and z_hi are 0, then the planet/sun is ; too close to be shown, so jump to PL2 to remove it ; from the screen, returning from the subroutine using a ; tail call JSR PROJ ; Project the planet/sun onto the screen, returning the ; centre's coordinates in K3(1 0) and K4(1 0) BCS PL2 ; If the C flag is set by PROJ then the planet/sun is ; not visible on-screen, so jump to PL2 to remove it ; from the screen, returning from the subroutine using ; a tail call LDA #96 ; Set (A P+1 P) = (0 96 0) = 24576 STA P+1 ; LDA #0 ; This represents the planet/sun's radius at a distance STA P ; of z = 1 JSR DVID3B2 ; Call DVID3B2 to calculate: ; ; K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_lo) ; = (0 96 0) / z ; = 24576 / z ; ; so K now contains the planet/sun's radius, reduced by ; the actual distance to the planet/sun. We know that ; K+3 and K+2 will be 0, as the number we are dividing, ; (0 96 0), fits into the two bottom bytes, so the ; result is actually in K(1 0) LDA K+1 ; If the high byte of the reduced radius is zero, jump BEQ PL82 ; to PL82, as K contains the radius on its own LDA #248 ; Otherwise set K = 248, to round up the radius in STA K ; K(1 0) to the nearest integer (if we consider the low ; byte to be the fractional part) .PL82 LDA TYPE ; If the planet/sun's type has bit 0 clear, then it's LSR A ; either 128 or 130, which is a planet (the sun has type BCC PL9 ; 129, which has bit 0 set). So jump to PL9 to draw the ; planet with radius K, returning from the subroutine ; using a tail call JMP SUN ; Otherwise jump to SUN to draw the sun with radius K, ; returning from the subroutine using a tail call ; ****************************************************************************** ; ; Name: PL9 (Part 1 of 3) ; Type: Subroutine ; Category: Drawing planets ; Summary: Draw the planet, with either an equator and meridian, or a crater ; ; ------------------------------------------------------------------------------ ; ; Draw the planet with radius K at pixel coordinate (K3, K4), and with either an ; equator and meridian, or a crater. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; K(1 0) The planet's radius ; ; K3(1 0) Pixel x-coordinate of the centre of the planet ; ; K4(1 0) Pixel y-coordinate of the centre of the planet ; ; INWK The planet's ship data block ; ; ****************************************************************************** .PL9 JSR WPLS2 ; Call WPLS2 to remove the planet from the screen JSR CIRCLE ; Call CIRCLE to draw the planet's new circle BCS PL20 ; If the call to CIRCLE returned with the C flag set, ; then the circle does not fit on-screen, so jump to ; PL20 to return from the subroutine LDA K+1 ; If K+1 is zero, jump to PL25 as K(1 0) < 256, so the BEQ PL25 ; planet fits on the screen and we can draw meridians or ; craters .PL20 RTS ; The planet doesn't fit on-screen, so return from the ; subroutine .PL25 LDA PLTOG ; If PLTOG is zero then planetary details are not BEQ PL20 ; enabled, so jump to PL20 to return from the subroutine ; ; In the original source the jump instruction is ; accompanied by a comment "sob!", which perhaps shows ; how sad the authors were to have to disable craters ; and meridians by default on the Commodore 64 LDA TYPE ; If the planet type is 128 then it has an equator and CMP #128 ; a meridian, so this jumps to PL26 if this is not a BNE PL26 ; planet with an equator - in other words, if it is a ; planet with a crater ; Otherwise this is a planet with an equator and ; meridian, so fall through into the following to draw ; them ; ****************************************************************************** ; ; Name: PL9 (Part 2 of 3) ; Type: Subroutine ; Category: Drawing planets ; Summary: Draw the planet's equator and meridian ; Deep dive: Drawing meridians and equators ; ; ------------------------------------------------------------------------------ ; ; Draw the planet's equator and meridian. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; K(1 0) The planet's radius ; ; K3(1 0) Pixel x-coordinate of the centre of the planet ; ; K4(1 0) Pixel y-coordinate of the centre of the planet ; ; INWK The planet's ship data block ; ; ****************************************************************************** LDA K ; If the planet's radius is less than 6, the planet is CMP #6 ; too small to show a meridian, so jump to PL20 to BCC PL20 ; return from the subroutine LDA INWK+14 ; Set P = -nosev_z_hi EOR #%10000000 STA P LDA INWK+20 ; Set A = roofv_z_hi JSR PLS4 ; Call PLS4 to calculate the following: ; ; CNT2 = arctan(P / A) / 4 ; = arctan(-nosev_z_hi / roofv_z_hi) / 4 ; ; and do the following if nosev_z_hi >= 0: ; ; CNT2 = CNT2 + PI LDX #9 ; Set X to 9 so the call to PLS1 divides nosev_x JSR PLS1 ; Call PLS1 to calculate the following: STA K2 ; STY XX16 ; (XX16 K2) = nosev_x / z ; ; and increment X to point to nosev_y for the next call JSR PLS1 ; Call PLS1 to calculate the following: STA K2+1 ; STY XX16+1 ; (XX16+1 K2+1) = nosev_y / z LDX #15 ; Set X to 15 so the call to PLS5 divides roofv_x JSR PLS5 ; Call PLS5 to calculate the following: ; ; (XX16+2 K2+2) = roofv_x / z ; ; (XX16+3 K2+3) = roofv_y / z JSR PLS2 ; Call PLS2 to draw the first meridian LDA INWK+14 ; Set P = -nosev_z_hi EOR #%10000000 STA P LDA INWK+26 ; Set A = sidev_z_hi, so the second meridian will be at ; 90 degrees to the first JSR PLS4 ; Call PLS4 to calculate the following: ; ; CNT2 = arctan(P / A) / 4 ; = arctan(-nosev_z_hi / sidev_z_hi) / 4 ; ; and do the following if nosev_z_hi >= 0: ; ; CNT2 = CNT2 + PI LDX #21 ; Set X to 21 so the call to PLS5 divides sidev_x JSR PLS5 ; Call PLS5 to calculate the following: ; ; (XX16+2 K2+2) = sidev_x / z ; ; (XX16+3 K2+3) = sidev_y / z JMP PLS2 ; Jump to PLS2 to draw the second meridian, returning ; from the subroutine using a tail call ; ****************************************************************************** ; ; Name: PL9 (Part 3 of 3) ; Type: Subroutine ; Category: Drawing planets ; Summary: Draw the planet's crater ; Deep dive: Drawing craters ; ; ------------------------------------------------------------------------------ ; ; Draw the planet's crater. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; K(1 0) The planet's radius ; ; K3(1 0) Pixel x-coordinate of the centre of the planet ; ; K4(1 0) Pixel y-coordinate of the centre of the planet ; ; INWK The planet's ship data block ; ; ****************************************************************************** .PL26 LDA INWK+20 ; Set A = roofv_z_hi BMI PL20 ; If A is negative, the crater is on the far side of the ; planet, so return from the subroutine (as PL2 ; contains an RTS) LDX #15 ; Set X = 15, so the following call to PLS3 operates on ; roofv JSR PLS3 ; Call PLS3 to calculate: ; ; (Y A P) = 222 * roofv_x / z ; ; to give the x-coordinate of the crater offset and ; increment X to point to roofv_y for the next call CLC ; Calculate: ADC K3 ; STA K3 ; K3(1 0) = (Y A) + K3(1 0) ; = 222 * roofv_x / z + x-coordinate of planet ; centre ; ; starting with the high bytes TYA ; And then doing the low bytes, so now K3(1 0) contains ADC K3+1 ; the x-coordinate of the crater offset plus the planet STA K3+1 ; centre to give the x-coordinate of the crater's centre JSR PLS3 ; Call PLS3 to calculate: ; ; (Y A P) = 222 * roofv_y / z ; ; to give the y-coordinate of the crater offset STA P ; Calculate: LDA K4 ; SEC ; K4(1 0) = K4(1 0) - (Y A) SBC P ; = 222 * roofv_y / z - y-coordinate of planet STA K4 ; centre ; ; starting with the low bytes STY P ; And then doing the low bytes, so now K4(1 0) contains LDA K4+1 ; the y-coordinate of the crater offset plus the planet SBC P ; centre to give the y-coordinate of the crater's centre STA K4+1 LDX #9 ; Set X = 9, so the following call to PLS1 operates on ; nosev JSR PLS1 ; Call PLS1 to calculate the following: ; ; (Y A) = nosev_x / z ; ; and increment X to point to nosev_y for the next call LSR A ; Set (XX16 K2) = (Y A) / 2 STA K2 STY XX16 JSR PLS1 ; Call PLS1 to calculate the following: ; ; (Y A) = nosev_y / z ; ; and increment X to point to nosev_z for the next call LSR A ; Set (XX16+1 K2+1) = (Y A) / 2 STA K2+1 STY XX16+1 LDX #21 ; Set X = 21, so the following call to PLS1 operates on ; sidev JSR PLS1 ; Call PLS1 to calculate the following: ; ; (Y A) = sidev_x / z ; ; and increment X to point to sidev_y for the next call LSR A ; Set (XX16+2 K2+2) = (Y A) / 2 STA K2+2 STY XX16+2 JSR PLS1 ; Call PLS1 to calculate the following: ; ; (Y A) = sidev_y / z ; ; and increment X to point to sidev_z for the next call LSR A ; Set (XX16+3 K2+3) = (Y A) / 2 STA K2+3 STY XX16+3 LDA #64 ; Set TGT = 64, so we draw a full ellipse in the call to STA TGT ; PLS22 below LDA #0 ; Set CNT2 = 0 as we are drawing a full ellipse, so we STA CNT2 ; don't need to apply an offset JMP PLS22 ; Jump to PLS22 to draw the crater, returning from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: PLS1 ; Type: Subroutine ; Category: Drawing planets ; Summary: Calculate (Y A) = nosev_x / z ; ; ------------------------------------------------------------------------------ ; ; Calculate the following division of a specified value from one of the ; orientation vectors (in this example, nosev_x): ; ; (Y A) = nosev_x / z ; ; where z is the z-coordinate of the planet from INWK. The result is an 8-bit ; magnitude in A, with maximum value 254, and just a sign bit (bit 7) in Y. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X Determines which of the INWK orientation vectors to ; divide: ; ; * X = 9, 11, 13: divides nosev_x, nosev_y, nosev_z ; ; * X = 15, 17, 19: divides roofv_x, roofv_y, roofv_z ; ; * X = 21, 23, 25: divides sidev_x, sidev_y, sidev_z ; ; INWK The planet's ship data block ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A The result as an 8-bit magnitude with maximum value 254 ; ; Y The sign of the result in bit 7 ; ; K+3 Also the sign of the result in bit 7 ; ; X X gets incremented by 2 so it points to the next ; coordinate in this orientation vector (so consecutive ; calls to the routine will start with x, then move onto y ; and then z) ; ; ****************************************************************************** .PLS1 LDA INWK,X ; Set P = nosev_x_lo STA P LDA INWK+1,X ; Set P+1 = |nosev_x_hi| AND #%01111111 STA P+1 LDA INWK+1,X ; Set A = sign bit of nosev_x_lo AND #%10000000 JSR DVID3B2 ; Call DVID3B2 to calculate: ; ; K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_lo) LDA K ; Fetch the lowest byte of the result into A LDY K+1 ; Fetch the second byte of the result into Y BEQ P%+4 ; If the second byte is 0, skip the next instruction LDA #254 ; The second byte is non-zero, so the result won't fit ; into one byte, so set A = 254 as our maximum one-byte ; value to return LDY K+3 ; Fetch the sign of the result from K+3 into Y INX ; Add 2 to X so the index points to the next coordinate INX ; in this orientation vector (so consecutive calls to ; the routine will start with x, then move onto y and z) RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: PLS2 ; Type: Subroutine ; Category: Drawing planets ; Summary: Draw a half-ellipse ; Deep dive: Drawing ellipses ; Drawing meridians and equators ; ; ------------------------------------------------------------------------------ ; ; Draw a half-ellipse, used for the planet's equator and meridian. ; ; ****************************************************************************** .PLS2 LDA #31 ; Set TGT = 31, so we only draw half an ellipse STA TGT ; Fall through into PLS22 to draw the half-ellipse ; ****************************************************************************** ; ; Name: PLS22 ; Type: Subroutine ; Category: Drawing planets ; Summary: Draw an ellipse or half-ellipse ; Deep dive: Drawing ellipses ; Drawing meridians and equators ; Drawing craters ; ; ------------------------------------------------------------------------------ ; ; Draw an ellipse or half-ellipse, to be used for the planet's equator and ; meridian (in which case we draw half an ellipse), or crater (in which case we ; draw a full ellipse). ; ; The ellipse is defined by a centre point, plus two conjugate radius vectors, ; u and v, where: ; ; u = [ u_x ] v = [ v_x ] ; [ u_y ] [ v_y ] ; ; The individual components of these 2D vectors (i.e. u_x, u_y etc.) are 16-bit ; sign-magnitude numbers, where the high bytes contain only the sign bit (in ; bit 7), with bits 0 to 6 being clear. This means that as we store u_x as ; (XX16 K2), for example, we know that |u_x| = K2. ; ; This routine calls BLINE to draw each line segment in the ellipse, passing the ; coordinates as follows: ; ; K6(1 0) = K3(1 0) + u_x * cos(CNT2) + v_x * sin(CNT2) ; ; K6(3 2) = K4(1 0) - u_y * cos(CNT2) - v_y * sin(CNT2) ; ; The y-coordinates are negated because BLINE expects pixel coordinates but the ; u and v vectors are extracted from the orientation vector. The y-axis runs ; in the opposite direction in 3D space to that on the screen, so we need to ; negate the 3D space coordinates before we can combine them with the ellipse's ; centre coordinates. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; K(1 0) The planet's radius ; ; K3(1 0) The pixel x-coordinate of the centre of the ellipse ; ; K4(1 0) The pixel y-coordinate of the centre of the ellipse ; ; (XX16 K2) The x-component of u (i.e. u_x), where XX16 contains ; just the sign of the sign-magnitude number ; ; (XX16+1 K2+1) The y-component of u (i.e. u_y), where XX16+1 contains ; just the sign of the sign-magnitude number ; ; (XX16+2 K2+2) The x-component of v (i.e. v_x), where XX16+2 contains ; just the sign of the sign-magnitude number ; ; (XX16+3 K2+3) The y-component of v (i.e. v_y), where XX16+3 contains ; just the sign of the sign-magnitude number ; ; TGT The number of segments to draw: ; ; * 32 for a half ellipse (a meridian) ; ; * 64 for a full ellipse (a crater) ; ; CNT2 The starting segment for drawing the half-ellipse ; ; ****************************************************************************** .PLS22 LDX #0 ; Set CNT = 0 STX CNT DEX ; Set FLAG = $FF to start a new line in the ball line STX FLAG ; heap when calling BLIN below, so the crater or ; meridian is separate from any previous ellipses .PLL4 LDA CNT2 ; Set X = CNT2 mod 32 AND #31 ; TAX ; So X is the starting segment, reduced to the range 0 ; to 32, so as there are 64 segments in the circle, this ; reduces the starting angle to 0 to 180 degrees, so we ; can use X as an index into the sine table (which only ; contains values for segments 0 to 31) ; ; Also, because CNT2 mod 32 is in the range 0 to 180 ; degrees, we know that sin(CNT2 mod 32) is always ; positive, or to put it another way: ; ; sin(CNT2 mod 32) = |sin(CNT2)| LDA SNE,X ; Set Q = sin(X) STA Q ; = sin(CNT2 mod 32) ; = |sin(CNT2)| LDA K2+2 ; Set A = K2+2 ; = |v_x| JSR FMLTU ; Set R = A * Q / 256 STA R ; = |v_x| * |sin(CNT2)| LDA K2+3 ; Set A = K2+3 ; = |v_y| JSR FMLTU ; Set K = A * Q / 256 STA K ; = |v_y| * |sin(CNT2)| LDX CNT2 ; If CNT2 >= 33 then this sets the C flag, otherwise CPX #33 ; it's clear, so this means that: ; ; * C is clear if the segment starts in the first half ; of the circle, 0 to 180 degrees ; ; * C is set if the segment starts in the second half ; of the circle, 180 to 360 degrees ; ; In other words, the C flag contains the sign bit for ; sin(CNT2), which is positive for 0 to 180 degrees ; and negative for 180 to 360 degrees LDA #0 ; Shift the C flag into the sign bit of XX16+5, so ROR A ; XX16+5 has the correct sign for sin(CNT2) STA XX16+5 ; ; Because we set the following above: ; ; K = |v_y| * |sin(CNT2)| ; R = |v_x| * |sin(CNT2)| ; ; we can add XX16+5 as the high byte to give us the ; following: ; ; (XX16+5 K) = |v_y| * sin(CNT2) ; (XX16+5 R) = |v_x| * sin(CNT2) LDA CNT2 ; Set X = (CNT2 + 16) mod 32 CLC ; ADC #16 ; So we can use X as a lookup index into the SNE table AND #31 ; to get the cosine (as there are 16 segments in a TAX ; quarter-circle) ; ; Also, because the sine table only contains positive ; values, we know that sin((CNT2 + 16) mod 32) will ; always be positive, or to put it another way: ; ; sin((CNT2 + 16) mod 32) = |cos(CNT2)| LDA SNE,X ; Set Q = sin(X) STA Q ; = sin((CNT2 + 16) mod 32) ; = |cos(CNT2)| LDA K2+1 ; Set A = K2+1 ; = |u_y| JSR FMLTU ; Set K+2 = A * Q / 256 STA K+2 ; = |u_y| * |cos(CNT2)| LDA K2 ; Set A = K2 ; = |u_x| JSR FMLTU ; Set P = A * Q / 256 STA P ; = |u_x| * |cos(CNT2)| ; ; The call to FMLTU also sets the C flag, so in the ; following, ADC #15 adds 16 rather than 15 LDA CNT2 ; If (CNT2 + 16) mod 64 >= 33 then this sets the C flag, ADC #15 ; otherwise it's clear, so this means that: AND #63 ; CMP #33 ; * C is clear if the segment starts in the first or ; last quarter of the circle, 0 to 90 degrees or 270 ; to 360 degrees ; ; * C is set if the segment starts in the second or ; third quarter of the circle, 90 to 270 degrees ; ; In other words, the C flag contains the sign bit for ; cos(CNT2), which is positive for 0 to 90 degrees or ; 270 to 360 degrees, and negative for 90 to 270 degrees LDA #0 ; Shift the C flag into the sign bit of XX16+4, so: ROR A ; XX16+4 has the correct sign for cos(CNT2) STA XX16+4 ; ; Because we set the following above: ; ; K+2 = |u_y| * |cos(CNT2)| ; P = |u_x| * |cos(CNT2)| ; ; we can add XX16+4 as the high byte to give us the ; following: ; ; (XX16+4 K+2) = |u_y| * cos(CNT2) ; (XX16+4 P) = |u_x| * cos(CNT2) LDA XX16+5 ; Set S = the sign of XX16+2 * XX16+5 EOR XX16+2 ; = the sign of v_x * XX16+5 STA S ; ; So because we set this above: ; ; (XX16+5 R) = |v_x| * sin(CNT2) ; ; we now have this: ; ; (S R) = v_x * sin(CNT2) LDA XX16+4 ; Set A = the sign of XX16 * XX16+4 EOR XX16 ; = the sign of u_x * XX16+4 ; ; So because we set this above: ; ; (XX16+4 P) = |u_x| * cos(CNT2) ; ; we now have this: ; ; (A P) = u_x * cos(CNT2) JSR ADD ; Set (A X) = (A P) + (S R) ; = u_x * cos(CNT2) + v_x * sin(CNT2) STA T ; Store the high byte in T, so the result is now: ; ; (T X) = u_x * cos(CNT2) + v_x * sin(CNT2) BPL PL42 ; If the result is positive, jump down to PL42 TXA ; The result is negative, so we need to negate the EOR #%11111111 ; magnitude using two's complement, first doing the low CLC ; byte in X ADC #1 TAX LDA T ; And then the high byte in T, making sure to leave the EOR #%01111111 ; sign bit alone ADC #0 STA T .PL42 TXA ; Set K6(1 0) = K3(1 0) + (T X) ADC K3 ; STA K6 ; starting with the low bytes LDA T ; And then doing the high bytes, so we now get: ADC K3+1 ; STA K6+1 ; K6(1 0) = K3(1 0) + (T X) ; = K3(1 0) + u_x * cos(CNT2) ; + v_x * sin(CNT2) ; ; K3(1 0) is the x-coordinate of the centre of the ; ellipse, so we now have the correct x-coordinate for ; our ellipse segment that we can pass to BLINE below LDA K ; Set R = K = |v_y| * sin(CNT2) STA R LDA XX16+5 ; Set S = the sign of XX16+3 * XX16+5 EOR XX16+3 ; = the sign of v_y * XX16+5 STA S ; ; So because we set this above: ; ; (XX16+5 K) = |v_y| * sin(CNT2) ; ; and we just set R = K, we now have this: ; ; (S R) = v_y * sin(CNT2) LDA K+2 ; Set P = K+2 = |u_y| * cos(CNT2) STA P LDA XX16+4 ; Set A = the sign of XX16+1 * XX16+4 EOR XX16+1 ; = the sign of u_y * XX16+4 ; ; So because we set this above: ; ; (XX16+4 K+2) = |u_y| * cos(CNT2) ; ; and we just set P = K+2, we now have this: ; ; (A P) = u_y * cos(CNT2) JSR ADD ; Set (A X) = (A P) + (S R) ; = u_y * cos(CNT2) + v_y * sin(CNT2) EOR #%10000000 ; Store the negated high byte in T, so the result is STA T ; now: ; ; (T X) = - u_y * cos(CNT2) - v_y * sin(CNT2) ; ; This negation is necessary because BLINE expects us ; to pass pixel coordinates, where y-coordinates get ; larger as we go down the screen; u_y and v_y, on the ; other hand, are extracted from the orientation ; vectors, where y-coordinates get larger as we go up ; in space, so to rectify this we need to negate the ; result in (T X) before we can add it to the ; y-coordinate of the ellipse's centre in BLINE BPL PL43 ; If the result is positive, jump down to PL43 TXA ; The result is negative, so we need to negate the EOR #%11111111 ; magnitude using two's complement, first doing the low CLC ; byte in X ADC #1 TAX LDA T ; And then the high byte in T, making sure to leave the EOR #%01111111 ; sign bit alone ADC #0 STA T .PL43 ; We now call BLINE to draw the ellipse line segment ; ; The first few instructions of BLINE do the following: ; ; K6(3 2) = K4(1 0) + (T X) ; ; which gives: ; ; K6(3 2) = K4(1 0) - u_y * cos(CNT2) ; - v_y * sin(CNT2) ; ; K4(1 0) is the pixel y-coordinate of the centre of the ; ellipse, so this gives us the correct y-coordinate for ; our ellipse segment (we already calculated the ; x-coordinate in K3(1 0) above) JSR BLINE ; Call BLINE to draw this segment, which also returns ; the updated value of CNT in A CMP TGT ; If CNT > TGT then jump to PL40 to stop drawing the BEQ P%+4 ; ellipse (which is how we draw half-ellipses) BCS PL40 LDA CNT2 ; Set CNT2 = (CNT2 + STP) mod 64 CLC ADC STP AND #63 STA CNT2 JMP PLL4 ; Jump back to PLL4 to draw the next segment .PL40 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: SUN (Part 1 of 4) ; Type: Subroutine ; Category: Drawing suns ; Summary: Draw the sun: Set up all the variables needed to draw the sun ; Deep dive: Drawing the sun ; ; ------------------------------------------------------------------------------ ; ; Draw a new sun with radius K at pixel coordinate (K3, K4), removing the old ; sun if there is one. This routine is used to draw the sun, as well as the ; star systems on the Short-range Chart. ; ; The first part sets up all the variables needed to draw the new sun. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; K The new sun's radius ; ; K3(1 0) Pixel x-coordinate of the centre of the new sun ; ; K4(1 0) Pixel y-coordinate of the centre of the new sun ; ; SUNX(1 0) The x-coordinate of the vertical centre axis of the old ; sun (the one currently on-screen) ; ; ****************************************************************************** .PLF3M3 JMP WPLS ; Jump to WPLS to remove the old sun from the screen. We ; only get here via the BCS just after the SUN entry ; point below, when there is no new sun to draw .PLF3 ; This is called from below to negate X and set A to ; $FF, for when the new sun's centre is off the bottom ; of the screen (so we don't need to draw its bottom ; half) ; ; This happens when the y-coordinate of the centre of ; the sun is bigger than the y-coordinate of the bottom ; of the space view TXA ; Negate X using two's complement, so X = ~X + 1 EOR #%11111111 CLC ADC #1 TAX .PLF17 ; This is called from below to set A to $FF, for when ; the new sun's centre is right on the bottom of the ; screen (so we don't need to draw its bottom half) LDA #$FF ; Set A = $FF JMP PLF5 ; Jump to PLF5 .SUN LDA #1 ; Set LSX = 1 to indicate the sun line heap is about to STA LSX ; be filled up JSR CHKON ; Call CHKON to check whether any part of the new sun's ; circle appears on-screen, and if it does, set P(2 1) ; to the maximum y-coordinate of the new sun on-screen BCS PLF3M3 ; If CHKON set the C flag then the new sun's circle does ; not appear on-screen, so jump to WPLS (via the JMP at ; the top of this routine) to remove the sun from the ; screen, returning from the subroutine using a tail ; call LDA #0 ; Set A = 0 LDX K ; Set X = K = radius of the new sun CPX #96 ; If X >= 96, set the C flag and rotate it into bit 0 ROL A ; of A, otherwise rotate a 0 into bit 0 CPX #40 ; If X >= 40, set the C flag and rotate it into bit 0 ROL A ; of A, otherwise rotate a 0 into bit 0 CPX #16 ; If X >= 16, set the C flag and rotate it into bit 0 ROL A ; of A, otherwise rotate a 0 into bit 0 ; By now, A contains the following: ; ; * If radius is 96-255 then A = %111 = 7 ; ; * If radius is 40-95 then A = %11 = 3 ; ; * If radius is 16-39 then A = %1 = 1 ; ; * If radius is 0-15 then A = %0 = 0 ; ; The value of A determines the size of the new sun's ; ragged fringes - the bigger the sun, the bigger the ; fringes .PLF18 STA CNT ; Store the fringe size in CNT ; We now calculate the highest pixel y-coordinate of the ; new sun, given that P(2 1) contains the 16-bit maximum ; y-coordinate of the new sun on-screen LDA Yx2M1 ; Set Y to the y-coordinate of the bottom of the space ; view LDX P+2 ; If P+2 is non-zero, the maximum y-coordinate is off BNE PLF2 ; the bottom of the screen, so skip to PLF2 with A set ; to the y-coordinate of the bottom of the space view CMP P+1 ; If A < P+1, the maximum y-coordinate is underneath the BCC PLF2 ; dashboard, so skip to PLF2 with A set to the ; y-coordinate of the bottom of the space view LDA P+1 ; Set A = P+1, the low byte of the maximum y-coordinate ; of the sun on-screen BNE PLF2 ; If A is non-zero, skip to PLF2 as it contains the ; value we are after LDA #1 ; Otherwise set A = 1, the top line of the screen .PLF2 STA TGT ; Set TGT to A, the maximum y-coordinate of the sun on ; screen ; We now calculate the number of lines we need to draw ; and the direction in which we need to draw them, both ; from the centre of the new sun LDA Yx2M1 ; Set (A X) = y-coordinate of bottom of screen - K4(1 0) SEC ; SBC K4 ; Starting with the low bytes TAX LDA #0 ; And then doing the high bytes, so (A X) now contains SBC K4+1 ; the number of lines between the centre of the sun and ; the bottom of the screen. If it is positive then the ; centre of the sun is above the bottom of the screen, ; if it is negative then the centre of the sun is below ; the bottom of the screen BMI PLF3 ; If A < 0, then this means the new sun's centre is off ; the bottom of the screen, so jump up to PLF3 to negate ; the height in X (so it becomes positive), set A to $FF ; and jump down to PLF5 BNE PLF4 ; If A > 0, then the new sun's centre is at least a full ; screen above the bottom of the space view, so jump ; down to PLF4 to set X = radius and A = 0 INX ; Set the flags depending on the value of X DEX BEQ PLF17 ; If X = 0 (we already know A = 0 by this point) then ; jump up to PLF17 to set A to $FF before jumping down ; to PLF5 CPX K ; If X < the radius in K, jump down to PLF5, so if BCC PLF5 ; X >= the radius in K, we set X = radius and A = 0 .PLF4 LDX K ; Set X to the radius LDA #0 ; Set A = 0 .PLF5 STX V ; Store the height in V STA V+1 ; Store the direction in V+1 LDA K ; Set (A P) = K * K JSR SQUA2 STA K2+1 ; Set K2(1 0) = (A P) = K * K LDA P STA K2 ; By the time we get here, the variables should be set ; up as shown in the header for part 3 below ; ****************************************************************************** ; ; Name: SUN (Part 2 of 4) ; Type: Subroutine ; Category: Drawing suns ; Summary: Draw the sun: Start from the bottom of the screen and erase the ; old sun line by line ; Deep dive: Drawing the sun ; ; ------------------------------------------------------------------------------ ; ; This part erases the old sun, starting at the bottom of the screen and working ; upwards until we reach the bottom of the new sun. ; ; ****************************************************************************** LDY Yx2M1 ; Set Y = y-coordinate of the bottom of the screen, ; which we use as a counter in the following routine to ; redraw the old sun LDA SUNX ; Set YY(1 0) = SUNX(1 0), the x-coordinate of the STA YY ; vertical centre axis of the old sun that's currently LDA SUNX+1 ; on-screen STA YY+1 .PLFL2 CPY TGT ; If Y = TGT, we have reached the line where we will BEQ PLFL ; start drawing the new sun, so there is no need to ; keep erasing the old one, so jump down to PLFL LDA LSO,Y ; Fetch the Y-th point from the sun line heap, which ; gives us the half-width of the old sun's line on this ; line of the screen BEQ PLF13 ; If A = 0, skip the following call to HLOIN2 as there ; is no sun line on this line of the screen JSR HLOIN2 ; Call HLOIN2 to draw a horizontal line on pixel line Y, ; with centre point YY(1 0) and half-width A, and remove ; the line from the sun line heap once done .PLF13 DEY ; Decrement the loop counter BNE PLFL2 ; Loop back for the next line in the line heap until ; we have either gone through the entire heap, or ; reached the bottom row of the new sun ; ****************************************************************************** ; ; Name: SUN (Part 3 of 4) ; Type: Subroutine ; Category: Drawing suns ; Summary: Draw the sun: Continue to move up the screen, drawing the new sun ; line by line ; Deep dive: Drawing the sun ; ; ------------------------------------------------------------------------------ ; ; This part draws the new sun. By the time we get to this point, the following ; variables should have been set up by parts 1 and 2: ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; V As we draw lines for the new sun, V contains the ; vertical distance between the line we're drawing and the ; centre of the new sun. As we draw lines and move up the ; screen, we either decrement (bottom half) or increment ; (top half) this value ; ; V+1 This determines which half of the new sun we are drawing ; as we work our way up the screen, line by line: ; ; * 0 means we are drawing the bottom half, so the lines ; get wider as we work our way up towards the centre, ; at which point we will move into the top half, and ; V+1 will switch to $FF ; ; * $FF means we are drawing the top half, so the lines ; get smaller as we work our way up, away from the ; centre ; ; TGT The maximum y-coordinate of the new sun on-screen (i.e. ; the screen y-coordinate of the bottom row of the new ; sun) ; ; CNT The fringe size of the new sun ; ; K2(1 0) The new sun's radius squared, i.e. K^2 ; ; Y The y-coordinate of the bottom row of the new sun ; ; ****************************************************************************** .PLFL LDA V ; Set (T P) = V * V JSR SQUA2 ; = V^2 STA T LDA K2 ; Set (R Q) = K^2 - V^2 SEC ; SBC P ; First calculating the low bytes STA Q LDA K2+1 ; And then doing the high bytes SBC T STA R STY Y1 ; Store Y in Y1, so we can restore it after the call to ; LL5 JSR LL5 ; Set Q = SQRT(R Q) ; = SQRT(K^2 - V^2) ; ; So Q contains the half-width of the new sun's line at ; height V from the sun's centre - in other words, it ; contains the half-width of the sun's line on the ; current pixel row Y LDY Y1 ; Restore Y from Y1 JSR DORND ; Set A and X to random numbers AND CNT ; Reduce A to a random number in the range 0 to CNT, ; where CNT is the fringe size of the new sun CLC ; Set A = A + Q ADC Q ; ; So A now contains the half-width of the sun on row ; V, plus a random variation based on the fringe size BCC PLF44 ; If the above addition did not overflow, skip the ; following instruction LDA #255 ; The above overflowed, so set the value of A to 255 ; So A contains the half-width of the new sun on pixel ; line Y, changed by a random amount within the size of ; the sun's fringe .PLF44 LDX LSO,Y ; Set X to the line heap value for the old sun's line ; at row Y STA LSO,Y ; Store the half-width of the new row Y line in the line ; heap BEQ PLF11 ; If X = 0 then there was no sun line on pixel row Y, so ; jump to PLF11 LDA SUNX ; Set YY(1 0) = SUNX(1 0), the x-coordinate of the STA YY ; vertical centre axis of the old sun that's currently LDA SUNX+1 ; on-screen STA YY+1 TXA ; Transfer the line heap value for the old sun's line ; from X into A JSR EDGES ; Call EDGES to calculate X1 and X2 for the horizontal ; line centred on YY(1 0) and with half-width A, i.e. ; the line for the old sun LDA X1 ; Store X1 and X2, the ends of the line for the old sun, STA XX ; in XX and XX+1 LDA X2 STA XX+1 LDA K3 ; Set YY(1 0) = K3(1 0), the x-coordinate of the centre STA YY ; of the new sun LDA K3+1 STA YY+1 LDA LSO,Y ; Fetch the half-width of the new row Y line from the ; line heap (which we stored above) JSR EDGES ; Call EDGES to calculate X1 and X2 for the horizontal ; line centred on YY(1 0) and with half-width A, i.e. ; the line for the new sun BCS PLF23 ; If the C flag is set, the new line doesn't fit on the ; screen, so jump to PLF23 to just draw the old line ; without drawing the new one ; At this point the old line is from XX to XX+1 and the ; new line is from X1 to X2, and both fit on-screen. We ; now want to remove the old line and draw the new one. ; We could do this by simply drawing the old one then ; drawing the new one, but instead Elite does this by ; drawing first from X1 to XX and then from X2 to XX+1, ; which you can see in action by looking at all the ; permutations below of the four points on the line and ; imagining what happens if you draw from X1 to XX and ; X2 to XX+1 using EOR logic. The six possible ; permutations are as follows, along with the result of ; drawing X1 to XX and then X2 to XX+1: ; ; X1 X2 XX____XX+1 -> +__+ + + ; ; X1 XX____X2____XX+1 -> +__+__+ + ; ; X1 XX____XX+1 X2 -> +__+__+__+ ; ; XX____X1____XX+1 X2 -> + +__+__+ ; ; XX____XX+1 X1 X2 -> + + +__+ ; ; XX____X1____X2____XX+1 -> + +__+ + ; ; They all end up with a line between X1 and X2, which ; is what we want. There's probably a mathematical proof ; of why this works somewhere, but the above is probably ; easier to follow. ; ; We can draw from X1 to XX and X2 to XX+1 by swapping ; XX and X2 and drawing from X1 to X2, and then drawing ; from XX to XX+1, so let's do this now LDA X2 ; Swap XX and X2 LDX XX STX X2 STA XX JSR HLOIN ; Draw a horizontal line from (X1, Y1) to (X2, Y1) .PLF23 ; If we jump here from the BCS above when there is no ; new line this will just draw the old line LDA XX ; Set X1 = XX STA X1 LDA XX+1 ; Set X2 = XX+1 STA X2 .PLF16 JSR HLOIN ; Draw a horizontal line from (X1, Y1) to (X2, Y1) .PLF6 DEY ; Decrement the line number in Y to move to the line ; above BEQ PLF8 ; If we have reached the top of the screen, jump to PLF8 ; as we are done drawing (the top line of the screen is ; the border, so we don't draw there) LDA V+1 ; If V+1 is non-zero then we are doing the top half of BNE PLF10 ; the new sun, so jump down to PLF10 to increment V and ; decrease the width of the line we draw DEC V ; Decrement V, the height of the sun that we use to work ; out the width, so this makes the line get wider, as we ; move up towards the sun's centre BNE PLFL ; If V is non-zero, jump back up to PLFL to do the next ; screen line up DEC V+1 ; Otherwise V is 0 and we have reached the centre of the ; sun, so decrement V+1 to -1 so we start incrementing V ; each time, thus doing the top half of the new sun .PLFLS JMP PLFL ; Jump back up to PLFL to do the next screen line up .PLF11 ; If we get here then there is no old sun line on this ; line, so we can just draw the new sun's line LDX K3 ; Set YY(1 0) = K3(1 0), the x-coordinate of the centre STX YY ; of the new sun's line LDX K3+1 STX YY+1 JSR EDGES ; Call EDGES to calculate X1 and X2 for the horizontal ; line centred on YY(1 0) and with half-width A, i.e. ; the line for the new sun BCC PLF16 ; If the line is on-screen, jump up to PLF16 to draw the ; line and loop round for the next line up LDA #0 ; The line is not on-screen, so set the line heap for STA LSO,Y ; line Y to 0, which means there is no sun line here BEQ PLF6 ; Jump up to PLF6 to loop round for the next line up ; (this BEQ is effectively a JMP as A is always zero) .PLF10 LDX V ; Increment V, the height of the sun that we use to work INX ; out the width, so this makes the line get narrower, as STX V ; we move up and away from the sun's centre CPX K ; If V <= the radius of the sun, we still have lines to BCC PLFLS ; draw, so jump up to PLFL (via PLFLS) to do the next BEQ PLFLS ; screen line up ; ****************************************************************************** ; ; Name: SUN (Part 4 of 4) ; Type: Subroutine ; Category: Drawing suns ; Summary: Draw the sun: Continue to the top of the screen, erasing the old ; sun line by line ; Deep dive: Drawing the sun ; ; ------------------------------------------------------------------------------ ; ; This part erases any remaining traces of the old sun, now that we have drawn ; all the way to the top of the new sun. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; RTS2 Contains an RTS ; ; ****************************************************************************** LDA SUNX ; Set YY(1 0) = SUNX(1 0), the x-coordinate of the STA YY ; vertical centre axis of the old sun that's currently LDA SUNX+1 ; on-screen STA YY+1 .PLFL3 LDA LSO,Y ; Fetch the Y-th point from the sun line heap, which ; gives us the half-width of the old sun's line on this ; line of the screen BEQ PLF9 ; If A = 0, skip the following call to HLOIN2 as there ; is no sun line on this line of the screen JSR HLOIN2 ; Call HLOIN2 to draw a horizontal line on pixel line Y, ; with centre point YY(1 0) and half-width A, and remove ; the line from the sun line heap once done .PLF9 DEY ; Decrement the line number in Y to move to the line ; above BNE PLFL3 ; Jump up to PLFL3 to redraw the next line up, until we ; have reached the top of the screen .PLF8 ; If we get here, we have successfully made it from the ; bottom line of the screen to the top, and the old sun ; has been replaced by the new one CLC ; Clear the C flag to indicate success in drawing the ; sun LDA K3 ; Set SUNX(1 0) = K3(1 0) STA SUNX LDA K3+1 STA SUNX+1 .RTS2 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: CIRCLE ; Type: Subroutine ; Category: Drawing circles ; Summary: Draw a circle for the planet ; Deep dive: Drawing circles ; ; ------------------------------------------------------------------------------ ; ; Draw a circle with the centre at (K3, K4) and radius K. Used to draw the ; planet's main outline. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; K The planet's radius ; ; K3(1 0) Pixel x-coordinate of the centre of the planet ; ; K4(1 0) Pixel y-coordinate of the centre of the planet ; ; ****************************************************************************** .CIRCLE JSR CHKON ; Call CHKON to check whether the circle fits on-screen BCS RTS2 ; If CHKON set the C flag then the circle does not fit ; on-screen, so return from the subroutine (as RTS2 ; contains an RTS) LDA #0 ; Set LSX2 = 0 to indicate that the ball line heap is STA LSX2 ; not empty, as we are about to fill it LDX K ; Set X = K = radius LDA #8 ; Set A = 8 CPX #8 ; If the radius < 8, skip to PL89 BCC PL89 LSR A ; Halve A so A = 4 CPX #60 ; If the radius < 60, skip to PL89 BCC PL89 LSR A ; Halve A so A = 2 .PL89 STA STP ; Set STP = A. STP is the step size for the circle, so ; the above sets a smaller step size for bigger circles ; ****************************************************************************** ; ; Name: CIRCLE2 ; Type: Subroutine ; Category: Drawing circles ; Summary: Draw a circle (for the planet or chart) ; Deep dive: Drawing circles ; ; ------------------------------------------------------------------------------ ; ; Draw a circle with the centre at (K3, K4) and radius K. Used to draw the ; planet and the chart circles. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; STP The step size for the circle ; ; K The circle's radius ; ; K3(1 0) Pixel x-coordinate of the centre of the circle ; ; K4(1 0) Pixel y-coordinate of the centre of the circle ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag The C flag is cleared ; ; ****************************************************************************** .CIRCLE2 LDX #$FF ; Set FLAG = $FF to reset the ball line heap in the call STX FLAG ; to the BLINE routine below INX ; Set CNT = 0, our counter that goes up to 64, counting STX CNT ; segments in our circle .PLL3 LDA CNT ; Set A = CNT JSR FMLTU2 ; Call FMLTU2 to calculate: ; ; A = K * sin(A) ; = K * sin(CNT) LDX #0 ; Set T = 0, so we have the following: STX T ; ; (T A) = K * sin(CNT) ; ; which is the x-coordinate of the circle for this count LDX CNT ; If CNT < 33 then jump to PL37, as this is the right CPX #33 ; half of the circle and the sign of the x-coordinate is BCC PL37 ; correct EOR #%11111111 ; This is the left half of the circle, so we want to ADC #0 ; flip the sign of the x-coordinate in (T A) using two's TAX ; complement, so we start with the low byte and store it ; in X (the ADC adds 1 as we know the C flag is set) LDA #$FF ; And then we flip the high byte in T ADC #0 STA T TXA ; Finally, we restore the low byte from X, so we have ; now negated the x-coordinate in (T A) CLC ; Clear the C flag so we can do some more addition below .PL37 ADC K3 ; We now calculate the following: STA K6 ; ; K6(1 0) = (T A) + K3(1 0) ; ; to add the coordinates of the centre to our circle ; point, starting with the low bytes LDA K3+1 ; And then doing the high bytes, so we now have: ADC T ; STA K6+1 ; K6(1 0) = K * sin(CNT) + K3(1 0) ; ; which is the result we want for the x-coordinate LDA CNT ; Set A = CNT + 16 CLC ADC #16 JSR FMLTU2 ; Call FMLTU2 to calculate: ; ; A = K * sin(A) ; = K * sin(CNT + 16) ; = K * cos(CNT) TAX ; Set X = A ; = K * cos(CNT) LDA #0 ; Set T = 0, so we have the following: STA T ; ; (T X) = K * cos(CNT) ; ; which is the y-coordinate of the circle for this count LDA CNT ; Set A = (CNT + 15) mod 64 ADC #15 AND #63 CMP #33 ; If A < 33 (i.e. CNT is 0-16 or 48-64) then jump to BCC PL38 ; PL38, as this is the bottom half of the circle and the ; sign of the y-coordinate is correct TXA ; This is the top half of the circle, so we want to EOR #%11111111 ; flip the sign of the y-coordinate in (T X) using two's ADC #0 ; complement, so we start with the low byte in X (the TAX ; ADC adds 1 as we know the C flag is set) LDA #$FF ; And then we flip the high byte in T, so we have ADC #0 ; now negated the y-coordinate in (T X) STA T CLC ; Clear the C flag so the addition at the start of BLINE ; will work .PL38 JSR BLINE ; Call BLINE to draw this segment, which also increases ; CNT by STP, the step size CMP #65 ; If CNT >= 65 then skip the next instruction BCS P%+5 JMP PLL3 ; Jump back for the next segment CLC ; Clear the C flag to indicate success RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: WPLS2 ; Type: Subroutine ; Category: Drawing planets ; Summary: Remove the planet from the screen ; Deep dive: The ball line heap ; ; ------------------------------------------------------------------------------ ; ; We do this by redrawing it using the lines stored in the ball line heap when ; the planet was originally drawn by the BLINE routine. ; ; ****************************************************************************** .WPLS2 LDY LSX2 ; If LSX2 is non-zero (which indicates the ball line BNE WP1 ; heap is empty), jump to WP1 to reset the line heap ; without redrawing the planet ; Otherwise Y is now 0, so we can use it as a counter to ; loop through the lines in the line heap, redrawing ; each one to remove the planet from the screen, before ; resetting the line heap once we are done .WPL1 CPY LSP ; If Y >= LSP then we have reached the end of the line BCS WP1 ; heap and have finished redrawing the planet (as LSP ; points to the end of the heap), so jump to WP1 to ; reset the line heap, returning from the subroutine ; using a tail call LDA LSY2,Y ; Set A to the y-coordinate of the current heap entry CMP #$FF ; If the y-coordinate is $FF, this indicates that the BEQ WP2 ; next point in the heap denotes the start of a line ; segment, so jump to WP2 to put it into (X1, Y1) STA Y2 ; Set (X2, Y2) to the x- and y-coordinates from the LDA LSX2,Y ; heap STA X2 JSR LOIN ; Draw a line from (X1, Y1) to (X2, Y2) INY ; Increment the loop counter to point to the next point LDA SWAP ; If SWAP is non-zero then we swapped the coordinates BNE WPL1 ; when filling the heap in BLINE, so loop back WPL1 ; for the next point in the heap LDA X2 ; Swap (X1, Y1) and (X2, Y2), so the next segment will STA X1 ; be drawn from the current (X2, Y2) to the next point LDA Y2 ; in the heap STA Y1 JMP WPL1 ; Loop back to WPL1 for the next point in the heap .WP2 INY ; Increment the loop counter to point to the next point LDA LSX2,Y ; Set (X1, Y1) to the x- and y-coordinates from the STA X1 ; heap LDA LSY2,Y STA Y1 INY ; Increment the loop counter to point to the next point JMP WPL1 ; Loop back to WPL1 for the next point in the heap ; ****************************************************************************** ; ; Name: WP1 ; Type: Subroutine ; Category: Drawing planets ; Summary: Reset the ball line heap ; ; ****************************************************************************** .WP1 LDA #1 ; Set LSP = 1 to reset the ball line heap pointer STA LSP LDA #$FF ; Set LSX2 = $FF to indicate the ball line heap is empty STA LSX2 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: WPLS ; Type: Subroutine ; Category: Drawing suns ; Summary: Remove the sun from the screen ; Deep dive: Drawing the sun ; ; ------------------------------------------------------------------------------ ; ; We do this by redrawing it using the lines stored in the sun line heap when ; the sun was originally drawn by the SUN routine. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; SUNX(1 0) The x-coordinate of the vertical centre axis of the sun ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; WPLS-1 Contains an RTS ; ; ****************************************************************************** .WPLS LDA LSX ; If LSX < 0, the sun line heap is empty, so return from BMI WPLS-1 ; the subroutine (as WPLS-1 contains an RTS) LDA SUNX ; Set YY(1 0) = SUNX(1 0), the x-coordinate of the STA YY ; vertical centre axis of the sun that's currently on LDA SUNX+1 ; screen STA YY+1 LDY #2*Y-1 ; #Y is the y-coordinate of the centre of the space ; view, so this sets Y as a counter for the number of ; lines in the space view (i.e. 191), which is also the ; number of lines in the LSO block .WPL2 LDA LSO,Y ; Fetch the Y-th point from the sun line heap, which ; gives us the half-width of the sun's line on this line ; of the screen BEQ P%+5 ; If A = 0, skip the following call to HLOIN2 as there ; is no sun line on this line of the screen JSR HLOIN2 ; Call HLOIN2 to draw a horizontal line on pixel line Y, ; with centre point YY(1 0) and half-width A, and remove ; the line from the sun line heap once done DEY ; Decrement the loop counter BNE WPL2 ; Loop back for the next line in the line heap until ; we have gone through the entire heap DEY ; This sets Y to $FF, as we end the loop with Y = 0 STY LSX ; Set LSX to $FF to indicate the sun line heap is empty RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: EDGES ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a horizontal line given a centre and a half-width ; ; ------------------------------------------------------------------------------ ; ; Set X1 and X2 to the x-coordinates of the ends of the horizontal line with ; centre x-coordinate YY(1 0), and length A in either direction from the centre ; (so a total line length of 2 * A). In other words, this line: ; ; X1 YY(1 0) X2 ; +-----------------+-----------------+ ; <- A -> <- A -> ; ; The resulting line gets clipped to the edges of the screen, if needed. If the ; calculation doesn't overflow, we return with the C flag clear, otherwise the C ; flag gets set to indicate failure and the Y-th LSO entry gets set to 0. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The half-length of the line ; ; YY(1 0) The centre x-coordinate ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Clear if the line fits on-screen, set if it doesn't ; ; X1, X2 The x-coordinates of the clipped line ; ; LSO+Y If the line doesn't fit, LSO+Y is set to 0 ; ; Y Y is preserved ; ; ****************************************************************************** .EDGES STA T ; Set T to the line's half-length in argument A CLC ; We now calculate: ADC YY ; STA X2 ; (A X2) = YY(1 0) + A ; ; to set X2 to the x-coordinate of the right end of the ; line, starting with the low bytes LDA YY+1 ; And then adding the high bytes ADC #0 BMI ED1 ; If the addition is negative then the calculation has ; overflowed, so jump to ED1 to return a failure BEQ P%+6 ; If the high byte A from the result is 0, skip the ; next two instructions, as the result already fits on ; the screen LDA #255 ; The high byte is positive and non-zero, so we went STA X2 ; past the right edge of the screen, so clip X2 to the ; x-coordinate of the right edge of the screen LDA YY ; We now calculate: SEC ; SBC T ; (A X1) = YY(1 0) - argument A STA X1 ; ; to set X1 to the x-coordinate of the left end of the ; line, starting with the low bytes LDA YY+1 ; And then subtracting the high bytes SBC #0 BNE ED3 ; If the high byte subtraction is non-zero, then skip ; to ED3 CLC ; Otherwise the high byte of the subtraction was zero, ; so the line fits on-screen and we clear the C flag to ; indicate success RTS ; Return from the subroutine .ED3 BPL ED1 ; If the addition is positive then the calculation has ; underflowed, so jump to ED1 to return a failure LDA #0 ; The high byte is negative and non-zero, so we went STA X1 ; past the left edge of the screen, so clip X1 to the ; x-coordinate of the left edge of the screen CLC ; The line does fit on-screen, so clear the C flag to ; indicate success RTS ; Return from the subroutine .ED1 LDA #0 ; Set the Y-th byte of the LSO block to 0 STA LSO,Y SEC ; The line does not fit on the screen, so set the C flag ; to indicate this result RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: CHKON ; Type: Subroutine ; Category: Drawing circles ; Summary: Check whether any part of a circle appears on the extended screen ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; K The circle's radius ; ; K3(1 0) Pixel x-coordinate of the centre of the circle ; ; K4(1 0) Pixel y-coordinate of the centre of the circle ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Clear if any part of the circle appears on-screen, set ; if none of the circle appears on-screen ; ; (A X) Minimum y-coordinate of the circle on-screen (i.e. the ; y-coordinate of the top edge of the circle) ; ; P(2 1) Maximum y-coordinate of the circle on-screen (i.e. the ; y-coordinate of the bottom edge of the circle) ; ; ****************************************************************************** .CHKON LDA K3 ; Set A = K3 + K CLC ADC K LDA K3+1 ; Set A = K3+1 + 0 + any carry from above, so this ADC #0 ; effectively sets A to the high byte of K3(1 0) + K: ; ; (A ?) = K3(1 0) + K ; ; so A is the high byte of the x-coordinate of the right ; edge of the circle BMI PL21 ; If A is negative then the right edge of the circle is ; to the left of the screen, so jump to PL21 to set the ; C flag and return from the subroutine, as the whole ; circle is off-screen to the left LDA K3 ; Set A = K3 - K SEC SBC K LDA K3+1 ; Set A = K3+1 - 0 - any carry from above, so this SBC #0 ; effectively sets A to the high byte of K3(1 0) - K: ; ; (A ?) = K3(1 0) - K ; ; so A is the high byte of the x-coordinate of the left ; edge of the circle BMI PL31 ; If A is negative then the left edge of the circle is ; to the left of the screen, and we already know the ; right edge is either on-screen or off-screen to the ; right, so skip to PL31 to move on to the y-coordinate ; checks, as at least part of the circle is on-screen in ; terms of the x-axis BNE PL21 ; If A is non-zero, then the left edge of the circle is ; to the right of the screen, so jump to PL21 to set the ; C flag and return from the subroutine, as the whole ; circle is off-screen to the right .PL31 LDA K4 ; Set P+1 = K4 + K CLC ADC K STA P+1 LDA K4+1 ; Set A = K4+1 + 0 + any carry from above, so this ADC #0 ; does the following: ; ; (A P+1) = K4(1 0) + K ; ; so A is the high byte of the y-coordinate of the ; bottom edge of the circle BMI PL21 ; If A is negative then the bottom edge of the circle is ; above the top of the screen, so jump to PL21 to set ; the C flag and return from the subroutine, as the ; whole circle is off-screen to the top STA P+2 ; Store the high byte in P+2, so now we have: ; ; P(2 1) = K4(1 0) + K ; ; i.e. the maximum y-coordinate of the circle on-screen ; (which we return) LDA K4 ; Set X = K4 - K SEC SBC K TAX LDA K4+1 ; Set A = K4+1 - 0 - any carry from above, so this SBC #0 ; does the following: ; ; (A X) = K4(1 0) - K ; ; so A is the high byte of the y-coordinate of the top ; edge of the circle BMI PL44 ; If A is negative then the top edge of the circle is ; above the top of the screen, and we already know the ; bottom edge is either on-screen or below the bottom ; of the screen, so skip to PL44 to clear the C flag and ; return from the subroutine using a tail call, as part ; of the circle definitely appears on-screen BNE PL21 ; If A is non-zero, then the top edge of the circle is ; below the bottom of the screen, so jump to PL21 to set ; the C flag and return from the subroutine, as the ; whole circle is off-screen to the bottom CPX Yx2M1 ; If we get here then A is zero, which means the top ; edge of the circle is within the screen boundary, so ; now we need to check whether it is in the space view ; (in which case it is on-screen) or the dashboard (in ; which case the top of the circle is hidden by the ; dashboard, so the circle isn't on-screen). We do this ; by checking the low byte of the result in X against ; Yx2M1, and returning the C flag from this comparison. ; The value in Yx2M1 is the y-coordinate of the bottom ; pixel row of the space view, so this does the ; following: ; ; * The C flag is set if coordinate (A X) is below the ; bottom row of the space view, i.e. the top edge of ; the circle is hidden by the dashboard ; ; * The C flag is clear if coordinate (A X) is above ; the bottom row of the space view, i.e. the top ; edge of the circle is on-screen RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: PL21 ; Type: Subroutine ; Category: Drawing planets ; Summary: Return from a planet/sun-drawing routine with a failure flag ; ; ------------------------------------------------------------------------------ ; ; Set the C flag and return from the subroutine. This is used to return from a ; planet- or sun-drawing routine with the C flag indicating an overflow in the ; calculation. ; ; ****************************************************************************** .PL21 SEC ; Set the C flag to indicate an overflow RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: PLS3 ; Type: Subroutine ; Category: Drawing planets ; Summary: Calculate (Y A P) = 222 * roofv_x / z ; ; ------------------------------------------------------------------------------ ; ; Calculate the following, with X determining the vector to use: ; ; (Y A P) = 222 * roofv_x / z ; ; though in reality only (Y A) is used. ; ; Although the code below supports a range of values of X, in practice the ; routine is only called with X = 15, and then again after X has been ; incremented to 17. So the values calculated by PLS1 use roofv_x first, then ; roofv_y. The comments below refer to roofv_x, for the first call. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X Determines which of the INWK orientation vectors to ; divide: ; ; * X = 15: divides roofv_x ; ; * X = 17: divides roofv_y ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; X X gets incremented by 2 so it points to the next ; coordinate in this orientation vector (so consecutive ; calls to the routine will start with x, then move onto y ; and then z) ; ; ****************************************************************************** .PLS3 JSR PLS1 ; Call PLS1 to calculate the following: STA P ; ; P = |roofv_x / z| ; K+3 = sign of roofv_x / z ; ; and increment X to point to roofv_y for the next call LDA #222 ; Set Q = 222, the offset to the crater STA Q STX U ; Store the vector index X in U for retrieval after the ; call to MULTU JSR MULTU ; Call MULTU to calculate ; ; (A P) = P * Q ; = 222 * |roofv_x / z| LDX U ; Restore the vector index from U into X LDY K+3 ; If the sign of the result in K+3 is positive, skip to BPL PL12 ; PL12 to return with Y = 0 EOR #$FF ; Otherwise the result should be negative, so negate the CLC ; high byte of the result using two's complement with ADC #1 ; A = ~A + 1 BEQ PL12 ; If A = 0, jump to PL12 to return with (Y A) = 0 LDY #$FF ; Set Y = $FF to be a negative high byte RTS ; Return from the subroutine .PL12 LDY #0 ; Set Y = 0 to be a positive high byte RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: PLS4 ; Type: Subroutine ; Category: Drawing planets ; Summary: Calculate CNT2 = arctan(P / A) / 4 ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; CNT2 = arctan(P / A) / 4 ; ; and do the following if nosev_z_hi >= 0: ; ; CNT2 = CNT2 + 32 ; ; which is the equivalent of adding 180 degrees to the result (or PI radians), ; as there are 64 segments in a full circle. ; ; This routine is called with the following arguments when calculating the ; equator and meridian for planets: ; ; * A = roofv_z_hi, P = -nosev_z_hi ; ; * A = sidev_z_hi, P = -nosev_z_hi ; ; So it calculates the angle between the planet's orientation vectors, in the ; z-axis. ; ; ****************************************************************************** .PLS4 STA Q ; Set Q = A JSR ARCTAN ; Call ARCTAN to calculate: ; ; A = arctan(P / Q) ; arctan(P / A) ; ; The result in A will be in the range 0 to 128, which ; represents an angle of 0 to 180 degrees (or 0 to PI ; radians) LDX INWK+14 ; If nosev_z_hi is negative, skip the following BMI P%+4 ; instruction to leave the angle in A as a positive ; integer in the range 0 to 128 (so when we calculate ; CNT2 below, it will be in the right half of the ; anti-clockwise arc that we describe when drawing ; circles, i.e. from 6 o'clock, through 3 o'clock and ; on to 12 o'clock) EOR #%10000000 ; If we get here then nosev_z_hi is positive, so flip ; bit 7 of the angle in A, which is the same as adding ; 128 to give a result in the range 129 to 256 (i.e. 129 ; to 0), or 180 to 360 degrees (so when we calculate ; CNT2 below, it will be in the left half of the ; anti-clockwise arc that we describe when drawing ; circles, i.e. from 12 o'clock, through 9 o'clock and ; on to 6 o'clock) LSR A ; Set CNT2 = A / 4 LSR A STA CNT2 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: PLS5 ; Type: Subroutine ; Category: Drawing planets ; Summary: Calculate roofv_x / z and roofv_y / z ; ; ------------------------------------------------------------------------------ ; ; Calculate the following divisions of a specified value from one of the ; orientation vectors (in this example, roofv): ; ; (XX16+2 K2+2) = roofv_x / z ; ; (XX16+3 K2+3) = roofv_y / z ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X Determines which of the INWK orientation vectors to ; divide: ; ; * X = 15: divides roofv_x and roofv_y ; ; * X = 21: divides sidev_x and sidev_y ; ; INWK The planet's ship data block ; ; ****************************************************************************** .PLS5 JSR PLS1 ; Call PLS1 to calculate the following: STA K2+2 ; STY XX16+2 ; K+2 = |roofv_x / z| ; XX16+2 = sign of roofv_x / z ; ; i.e. (XX16+2 K2+2) = roofv_x / z ; ; and increment X to point to roofv_y for the next call JSR PLS1 ; Call PLS1 to calculate the following: STA K2+3 ; STY XX16+3 ; K+3 = |roofv_y / z| ; XX16+3 = sign of roofv_y / z ; ; i.e. (XX16+3 K2+3) = roofv_y / z ; ; and increment X to point to roofv_z for the next call RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: PLS6 ; Type: Subroutine ; Category: Drawing planets ; Summary: Calculate (X K) = (A P+1 P) / (z_sign z_hi z_lo) ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; (X K) = (A P+1 P) / (z_sign z_hi z_lo) ; ; returning an overflow in the C flag if the result is >= 1024. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; INWK The planet or sun's ship data block ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if the result >= 1024, clear otherwise ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; PL44 Clear the C flag and return from the subroutine ; ; PL6 Contains an RTS ; ; ****************************************************************************** .PLS6 JSR DVID3B2 ; Call DVID3B2 to calculate: ; ; K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_lo) LDA K+3 ; Set A = |K+3| OR K+2 AND #%01111111 ORA K+2 BNE PL21 ; If A is non-zero then the two high bytes of K(3 2 1 0) ; are non-zero, so jump to PL21 to set the C flag and ; return from the subroutine ; We can now just consider K(1 0), as we know the top ; two bytes of K(3 2 1 0) are both 0 LDX K+1 ; Set X = K+1, so now (X K) contains the result in ; K(1 0), which is the format we want to return the ; result in CPX #4 ; If the high byte of K(1 0) >= 4 then the result is BCS PL6 ; >= 1024, so return from the subroutine with the C flag ; set to indicate an overflow (as PL6 contains an RTS) LDA K+3 ; Fetch the sign of the result from K+3 (which we know ; has zeroes in bits 0-6, so this just fetches the sign) ;CLC ; This instruction is commented out in the original ; source. It would have no effect as we know the C flag ; is already clear, as we skipped past the BCS above BPL PL6 ; If the sign bit is clear and the result is positive, ; then the result is already correct, so return from ; the subroutine with the C flag clear to indicate ; success (as PL6 contains an RTS) LDA K ; Otherwise we need to negate the result, which we do EOR #%11111111 ; using two's complement, starting with the low byte: ADC #1 ; STA K ; K = ~K + 1 TXA ; And then the high byte: EOR #%11111111 ; ADC #0 ; X = ~X TAX .PL44 CLC ; Clear the C flag to indicate success .PL6 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: YESNO ; Type: Subroutine ; Category: Keyboard ; Summary: Wait until either "Y" or "N" is pressed ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if "Y" was pressed, clear if "N" was pressed ; ; ****************************************************************************** .YESNO JSR t ; Scan the keyboard until a key is pressed, returning ; the ASCII code in A and X CMP #'Y' ; If "Y" was pressed, return from the subroutine with BEQ PL6 ; the C flag set (as the CMP sets the C flag, and PL6 ; contains an RTS) CMP #'N' ; If "N" was not pressed, loop back to keep scanning BNE YESNO ; for key presses CLC ; Clear the C flag RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TT17 ; Type: Subroutine ; Category: Keyboard ; Summary: Scan the keyboard for cursor key or joystick movement ; ; ------------------------------------------------------------------------------ ; ; Scan the keyboard and joystick for cursor key or stick movement, and return ; the result as deltas (changes) in x- and y-coordinates as follows: ; ; * For joystick, X and Y are integers between -1 and +1 depending on the ; stick direction and if the fire button is not being pressed, or -4 and +4 ; if the fire button is being pressed ; ; * For keyboard, X and Y are integers between -1 and +1 depending on which ; keys are pressed and if RETURN is not being pressed, or -4 and +4 if ; RETURN is being pressed ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A The key pressed, if the arrow keys were used ; ; X Change in the x-coordinate according to the cursor keys ; being pressed or joystick movement, as an integer (see ; above) ; ; Y Change in the y-coordinate according to the cursor keys ; being pressed or joystick movement, as an integer (see ; above) ; ; ****************************************************************************** .TT17 LDA QQ11 ; If this not the space view, skip the following three BNE TT17afterall ; instructions to move onto the cursor key logic JSR DOKEY ; This is the space view, so scan the keyboard for ; flight controls and pause keys, (or the equivalent on ; joystick) and update the key logger, setting KL to the ; key pressed TXA ; Transfer the value of the key pressed from X to A RTS ; Return from the subroutine .TT17afterall JSR DOKEY ; Scan the keyboard for flight controls and pause keys, ; (or the equivalent on joystick) and update the key ; logger, setting KL to the key pressed LDA JSTK ; If the joystick is not configured, jump down to TJ1, BEQ TJ1 ; otherwise we move the cursor with the joystick ; We now move the cursor according to the joystick's ; position, moving the cursor much faster if the fire ; button is held down LDA KY3 ; Set A to the status of the roll left joystick axis in ; KY4, so A is 0 if it not being "pressed" or $FF if it ; is (where $FF is -1 as a signed integer) BIT KY4 ; If bit 7 of KY4 is set then the roll right joystick BPL P%+4 ; axis is being "pressed", so set A = 1 LDA #1 ; By this point A is: ; ; * 1 if the joystick is moving right ; ; * -1 if the joystick is moving left ; ; * 0 if neither are being pressed BIT KY7 ; If bit 7 of KY7 is set then the joystick fire button BPL P%+4 ; is being pressed, so shift A to the left by two places ASL A ; so it is now 4, -4 or 0 ASL A TAX ; Store the result in X, so X contains the joystick ; x-direction as a signed and scaled number LDA KY5 ; Set A to the status of the pull up joystick axis in ; KY5, so A is 0 if it not being "pressed" or $FF if it ; is (where $FF is -1 as a signed integer) BIT KY6 ; If bit 7 of KY4 is set then the pitch down joystick BPL P%+4 ; axis is being "pressed", so set A = 1 LDA #1 ; By this point A is: ; ; * 1 if the joystick is moving up ; ; * -1 if the joystick is moving down ; ; * 0 if neither are being pressed BIT KY7 ; If bit 7 of KY7 is set then the joystick fire button BPL P%+4 ; is being pressed, so shift A to the left by two places ASL A ; so it is now 4, -4 or 0 ASL A TAY ; Store the result in Y, so Y contains the joystick ; y-direction as a signed and scaled number LDA #0 ; Zero the pitch, roll and fire keys in the key logger STA KY3 ; so they don't get processed again in this iteration of STA KY4 ; the main loop STA KY5 STA KY6 STA KY7 LDA thiskey ; Fetch the key pressed from thiskey in the key logger, ; so the routine returns with the key pressed in A RTS ; Return from the subroutine .TJ1 LDA KLO+$3E ; If the key logger entry for cursor left/right in BEQ noxmove ; KLO+$3E is zero, then the left/right cursor key is not ; being pressed, so jump to noxmove with A = 0 LDA #1 ; Set A = 1 to indicate movement in the x-direction ORA KLO+$31 ; If either left SHIFT (KLO+$31) or right SHIFT (KLO+$C) ORA KLO+$C ; are being pressed, then their key logger entries will ; be $FF, so this sets A to $FF (i.e. -1) if a SHIFT key ; is being pressed .noxmove ; By this point A is: ; ; * 1 if cursor left/right is being pressed ; ; * -1 if SHIFT-cursor left/right is being pressed ; ; * 0 if neither are being pressed BIT KLO+$3F ; If bit 7 of KLO+$3F is set then the RETURN button is BPL P%+4 ; being pressed, so shift A to the left by two places ASL A ; so it is now 4, -4 or 0 ASL A TAX ; Store the result in X, so X contains the cursor key ; x-direction as a signed and scaled number LDA KLO+$39 ; If the key logger entry for cursor up/down in KLO+$39 BEQ noymove ; is zero, then the up/down cursor key is not being ; pressed, so jump to noymove with A = 0 LDA #1 ; Set A = 1 to indicate movement in the y-direction ORA KLO+$31 ; If either left SHIFT (KLO+$31) or right SHIFT (KLO+$C) ORA KLO+$C ; are being pressed, then their key logger entries will ; be $FF, so this sets A to $FF (i.e. -1) if a SHIFT key ; is being pressed EOR #%11111110 ; Flip the sign of A using a simple two's complement, ; which works because A is either 1 or -1 .noymove ; By this point A is: ; ; * -1 if cursor up/down is being pressed ; ; * 1 if SHIFT-cursor up/down is being pressed ; ; * 0 if neither are being pressed BIT KLO+$3F ; If bit 7 of KLO+$3F is set then the RETURN button is BPL P%+4 ; being pressed, so shift A to the left by two places ASL A ; so it is now 4, -4 or 0 ASL A TAY ; Store the result in Y, so Y contains the cursor key ; y-direction as a signed and scaled number LDA thiskey ; Fetch the key pressed from thiskey in the key logger, ; so the routine returns with the key pressed in A RTS ; Return from the subroutine ; ****************************************************************************** ; ; Save ELTE.bin ; ; ****************************************************************************** PRINT "ELITE E" PRINT "Assembled at ", ~CODE_E% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_E%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_E% PRINT "S.ELTE ", ~CODE_E%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_E% SAVE "3-assembled-output/ELTE.bin", CODE_E%, P%, LOAD% ; ****************************************************************************** ; ; ELITE F FILE ; ; Produces the binary file ELTF.bin that gets loaded by elite-checksum.py. ; ; ****************************************************************************** CODE_F% = P% LOAD_F% = LOAD% + P% - CODE% ; ****************************************************************************** ; ; Name: SWAPPZERO (source disk variant) ; Type: Subroutine ; Category: Utility routines ; Summary: A routine that swaps zero page with the page at $CE00, so that ; zero page changes made by Kernal functions can be reversed ; ; ****************************************************************************** IF _SOURCE_DISK .SWAPPZERO LDX #K3+1 ; This routine starts copying zero page from the byte ; after K3 and up, using X as an index .SWPZL LDA ZP,X ; Swap the X-th byte of zero page with the X-th byte of LDY $CE00,X ; $CE00 STA $CE00,X STY ZP,X INX ; Increment the loop counter BNE SWPZL ; Loop back for the next byte RTS ; Return from the subroutine ENDIF ; ****************************************************************************** ; ; Name: NOSPRITES ; Type: Subroutine ; Category: Missions ; Summary: Disable all sprites and remove them from the screen ; ; ****************************************************************************** .NOSPRITES LDA #%101 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 1 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on ; ; See the memory map at the top of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore LDA #%00000000 ; Clear bits 0 to 7 of VIC register $15 to disable all STA VIC+$15 ; eight sprites IF NOT(USA%) ; We only include this code if USA% is FALSE ; ; It is designed to slow down PAL machines to match the ; speed of NTSC machines (though the GMA86 PAL version ; doesn't actually include this code) ; ; Specifically, it waits until raster line 256 + PALCK ; is reached before continuing LDA #PALCK ; Set A = PALCK, which contains the bottom byte of the ; the raster line that we want to wait for .UKCHK2 BIT VIC+$11 ; Loop back to UKCHK2 until bit 7 of VIC-II register $11 BPL UKCHK2 ; (control register 1) is set ; ; Bit 7 of register $11 contains the top bit of the ; current raster line (which is a 9-bit value), so this ; waits until the raster has reached at least line 256 CMP VIC+$12 ; Loop back to UKCHK2 until VIC-II register $12 equals BNE UKCHK2 ; PALCK ; ; VIC-II register $12 contains the bottom byte of the ; current raster line (which is a 9-bit value), and we ; only get here when the top bit of the raster line is ; set, so this waits until we have reached raster line ; 256 + PALCK ENDIF LDA #%100 ; Set A = %100 and fall through into SETL1 to set the ; 6510 input/output port to the following: ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore ; ****************************************************************************** ; ; Name: SETL1 ; Type: Subroutine ; Category: Utility routines ; Summary: Set the 6510 input/output port register to control the memory map ; ; ------------------------------------------------------------------------------ ; ; See page 260 of the Programmer's Reference Guide for details on how the 6510 ; input/output port register works. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The new value of the input/output port register: ; ; * Bit 0 controls LORAM ; ; * Bit 1 controls HIRAM ; ; * Bit 2 controls CHAREN ; ; ****************************************************************************** .SETL1 SEI ; Disable interrupts while we set the 6510 input/output ; port register STA L1M ; Store the new value of the port register in L1M LDA l1 ; Set bits 0 to 2 of the port register at location l1 AND #%11111000 ; ($0001) to bits 0 to 2 of L1M, leaving bits 3 to 7 ORA L1M ; unchanged STA l1 ; ; This sets LORAM, HIRAM and CHAREN to the new values CLI ; Re-enable interrupts RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: L1M ; Type: Variable ; Category: Utility routines ; Summary: Temporary storage for the new value of the 6510 input/output port ; register ; ; ****************************************************************************** .L1M EQUB %100 ; By default, this sets the 6510 input/output port to ; the following: ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore ; ****************************************************************************** ; ; Name: KS3 ; Type: Subroutine ; Category: Universe ; Summary: Set the SLSP ship line heap pointer after shuffling ship slots ; ; ------------------------------------------------------------------------------ ; ; The final part of the KILLSHP routine, called after we have shuffled the ship ; slots and sorted out our missiles. This simply sets SLSP to the new bottom of ; the ship line heap. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; P(1 0) Points to the ship line heap of the ship in the last ; occupied slot (i.e. it points to the bottom of the ; descending heap) ; ; ****************************************************************************** .KS3 LDA P ; After shuffling the ship slots, P(1 0) will point to STA SLSP ; the new bottom of the ship line heap, so store this in LDA P+1 ; SLSP(1 0), which stores the bottom of the heap STA SLSP+1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: KS1 ; Type: Subroutine ; Category: Universe ; Summary: Remove the current ship from our local bubble of universe ; ; ------------------------------------------------------------------------------ ; ; Part 12 of the main flight loop calls this routine to remove the ship that is ; currently being analysed by the flight loop. Once the ship is removed, it ; jumps back to MAL1 to rejoin the main flight loop, with X pointing to the ; same slot that we just cleared (and which now contains the next ship in the ; local bubble of universe). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; XX0 The address of the blueprint for this ship ; ; INF The address of the data block for this ship ; ; ****************************************************************************** .KS1 LDX XSAV ; Fetch the current ship's slot number from XSAV JSR KILLSHP ; Call KILLSHP to remove the ship in slot X from our ; local bubble of universe LDX XSAV ; Restore the current ship's slot number from XSAV, ; which now points to the next ship in the bubble JMP MAL1 ; Jump to MAL1 to rejoin the main flight loop at the ; start of the ship analysis loop ; ****************************************************************************** ; ; Name: KS4 ; Type: Subroutine ; Category: Universe ; Summary: Remove the space station and replace it with the sun ; ; ****************************************************************************** .KS4 JSR ZINF ; Call ZINF to reset the INWK ship workspace JSR FLFLLS ; Reset the LSO block, returns with A = 0 STA FRIN+1 ; Set the second slot in the FRIN table to 0, which ; sets this slot to empty, so when we call NWSHP below ; the new sun that gets created will go into FRIN+1 STA SSPR ; Set the "space station present" flag to 0, as we are ; no longer in the space station's safe zone JSR SPBLB ; Call SPBLB to redraw the space station bulb, which ; will erase it from the dashboard LDA #6 ; Set the sun's y_sign to 6 STA INWK+5 LDA #129 ; Set A = 129, the ship type for the sun JMP NWSHP ; Call NWSHP to set up the sun's data block and add it ; to FRIN, where it will get put in the second slot as ; we just cleared out the second slot, and the first ; slot is already taken by the planet ; ****************************************************************************** ; ; Name: KS2 ; Type: Subroutine ; Category: Universe ; Summary: Check the local bubble for missiles with target lock ; ; ------------------------------------------------------------------------------ ; ; Check the local bubble of universe to see if there are any missiles with ; target lock in the vicinity. If there are, then check their targets; if we ; just removed their target in the KILLSHP routine, then switch off their AI so ; they just drift in space, otherwise update their targets to reflect the newly ; shuffled slot numbers. ; ; This is called from KILLSHP once the slots have been shuffled down, following ; the removal of a ship. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; XX4 The slot number of the ship we removed just before ; calling this routine ; ; ****************************************************************************** .KS2 LDX #$FF ; We want to go through the ships in our local bubble ; and pick out all the missiles, so set X to $FF to ; use as a counter .KSL4 INX ; Increment the counter (so it starts at 0 on the first ; iteration) LDA FRIN,X ; If slot X is empty then we have worked our way through BEQ KS3 ; all the slots, so jump to KS3 to stop looking CMP #MSL ; If the slot does not contain a missile, loop back to BNE KSL4 ; KSL4 to check the next slot ; We have found a slot containing a missile, so now we ; want to check whether it has target lock TXA ; Set Y = X * 2 and fetch the Y-th address from UNIV ASL A ; and store it in SC and SC+1 - in other words, set TAY ; SC(1 0) to point to the missile's ship data block LDA UNIV,Y STA SC LDA UNIV+1,Y STA SC+1 LDY #32 ; Fetch byte #32 from the missile's ship data (AI) LDA (SC),Y BPL KSL4 ; If bit 7 of byte #32 is clear, then the missile is ; dumb and has no AI, so loop back to KSL4 to move on ; to the next slot AND #%01111111 ; Otherwise this missile has AI, so clear bit 7 and LSR A ; shift right to set the C flag to the missile's "is ; locked" flag, and A to the target's slot number CMP XX4 ; If this missile's target is less than XX4, then the BCC KSL4 ; target's slot isn't being shuffled down, so jump to ; KSL4 to move on to the next slot BEQ KS6 ; If this missile was locked onto the ship that we just ; removed in KILLSHP, jump to KS6 to stop the missile ; from continuing to hunt it down SBC #1 ; Otherwise this missile is locked and has AI enabled, ; and its target will have moved down a slot, so ; subtract 1 from the target number (we know C is set ; from the BCC above) ASL A ; Shift the target number left by 1, so it's in bits ; 1-6 once again, and also set bit 0 to 1, as the C ; flag is still set, so this makes sure the missile is ; still set to being locked ORA #%10000000 ; Set bit 7, so the missile's AI is enabled STA (SC),Y ; Update the missile's AI flag to the value in A BNE KSL4 ; Loop back to KSL4 to move on to the next slot (this ; BNE is effectively a JMP as A will never be zero) .KS6 LDA #0 ; The missile's target lock just got removed, so set the STA (SC),Y ; AI flag to 0 to make it dumb and not locked BEQ KSL4 ; Loop back to KSL4 to move on to the next slot (this ; BEQ is effectively a JMP as A is always zero) ; ****************************************************************************** ; ; Name: KILLSHP ; Type: Subroutine ; Category: Universe ; Summary: Remove a ship from our local bubble of universe ; ; ------------------------------------------------------------------------------ ; ; Remove the ship in slot X from our local bubble of universe. This happens ; when we kill a ship, collide with a ship and destroy it, or when a ship moves ; outside our local bubble. ; ; We also use this routine when we move out of range of the space station, in ; which case we replace it with the sun. ; ; When removing a ship, this creates a gap in the ship slots at FRIN, so we ; shuffle all the later slots down to close the gap. We also shuffle the ship ; data blocks at K% and ship line heap at WP, to reclaim all the memory that ; the removed ship used to occupy. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The slot number of the ship to remove ; ; XX0 The address of the blueprint for the ship to remove ; ; INF The address of the data block for the ship to remove ; ; ****************************************************************************** .KILLSHP STX XX4 ; Store the slot number of the ship to remove in XX4 LDA MSTG ; Check whether this slot matches the slot number in CMP XX4 ; MSTG, which is the target of our missile lock BNE KS5 ; If our missile is not locked on this ship, jump to KS5 LDY #GREEN2 ; Otherwise we need to remove our missile lock, so call JSR ABORT ; ABORT to unarm the missile and update the missile ; indicators on the dashboard to green (Y = #GREEN2) LDA #200 ; Print recursive token 40 ("TARGET LOST") as an JSR MESS ; in-flight message .KS5 LDY XX4 ; Restore the slot number of the ship to remove into Y LDX FRIN,Y ; Fetch the contents of the slot, which contains the ; ship type CPX #SST ; If this is the space station, then jump to KS4 to BEQ KS4 ; replace the space station with the sun CPX #CON ; Did we just kill the Constrictor from mission 1? If BNE lll ; not, jump to lll LDA TP ; We just killed the Constrictor from mission 1, so set ORA #%00000010 ; bit 1 of TP to indicate that we have successfully STA TP ; completed mission 1 INC TALLY+1 ; Award 256 kill points for killing the Constrictor .lll CPX #HER ; Did we just kill a rock hermit? If we did, jump to BEQ blacksuspenders ; blacksuspenders to decrease the junk count CPX #JL ; If JL <= X < JH, i.e. the type of ship we killed in X BCC KS7 ; is junk (escape pod, alloy plate, cargo canister, CPX #JH ; asteroid, splinter, Shuttle or Transporter), then keep BCS KS7 ; going, otherwise jump to KS7 .blacksuspenders DEC JUNK ; We just killed junk, so decrease the junk counter .KS7 DEC MANY,X ; Decrease the number of this type of ship in our little ; bubble, which is stored in MANY+X (where X is the ship ; type) LDX XX4 ; Restore the slot number of the ship to remove into X ; We now want to remove this ship and reclaim all the ; memory that it uses. Removing the ship will leave a ; gap in three places, which we need to close up: ; ; * The ship slots in FRIN ; ; * The ship data blocks in K% ; ; * The descending ship line heap at WP down ; ; The rest of this routine closes up these gaps by ; looping through all the occupied ship slots after the ; slot we are removing, one by one, and shuffling each ; ship's slot, data block and line heap down to close ; up the gaps left by the removed ship. As part of this, ; we have to make sure we update any address pointers ; so they point to the newly shuffled data blocks and ; line heaps ; ; In the following, when shuffling a ship's data down ; into the preceding empty slot, we call the ship that ; we are shuffling down the "source", and we call the ; empty slot we are shuffling it into the "destination" ; ; Before we start looping through the ships we need to ; shuffle down, we need to set up some variables to ; point to the source and destination line heaps LDY #5 ; Fetch byte #5 of the removed ship's blueprint into A, LDA (XX0),Y ; which gives the ship's maximum heap size for the ship ; we are removing (i.e. the size of the gap in the heap ; created by the ship removal) ; INF currently contains the ship data for the ship we ; are removing, and INF(34 33) contains the address of ; the bottom of the ship's heap, so we can calculate ; the address of the top of the heap by adding the heap ; size to this address LDY #33 ; First we add A and the address in INF+33, to get the CLC ; low byte of the top of the heap, which we store in P ADC (INF),Y STA P INY ; And next we add A and the address in INF+34, with any LDA (INF),Y ; carry from the previous addition, to get the high byte ADC #0 ; of the top of the heap, which we store in P+1, so STA P+1 ; P(1 0) points to the top of this ship's heap ; Now, we're ready to start looping through the ships ; we want to move, moving the slots, data blocks and ; line heap from the source to the destination. In the ; following, we set up SC to point to the source data, ; and INF (which currently points to the removed ship's ; data that we can now overwrite) points to the ; destination ; ; So P(1 0) now points to the top of the line heap for ; the destination .KSL1 INX ; On entry, X points to the empty slot we want to ; shuffle the next ship into (the destination), so ; this increment points X to the next slot - i.e. the ; source slot we want to shuffle down LDA FRIN,X ; Copy the contents of the source slot into the STA FRIN-1,X ; destination slot BNE P%+5 ; If the slot we just shuffled down is not empty, then ; skip the following instruction JMP KS2 ; The source slot is empty and we are done shuffling, ; so jump to KS2 to move on to processing missiles ASL A ; Otherwise we have a source ship to shuffle down into TAY ; the destination, so set Y = A * 2 so it can act as an ; index into the two-byte ship blueprint lookup table ; at XX21 for the source ship LDA XX21-2,Y ; Set SC(0 1) to point to the blueprint data for the STA SC ; source ship LDA XX21-1,Y STA SC+1 LDY #5 ; Fetch blueprint byte #5 for the source ship, which LDA (SC),Y ; gives us its maximum heap size, and store it in T STA T ; We now subtract T from P(1 0), so P(1 0) will point to ; the bottom of the line heap for the destination ; (which we will use later when closing up the gap in ; the heap space) LDA P ; First, we subtract the low bytes SEC SBC T STA P LDA P+1 ; And then we do the high bytes, for which we subtract SBC #0 ; 0 to include any carry, so this is effectively doing STA P+1 ; P(1 0) = P(1 0) - (0 T) ; Next, we want to set SC(1 0) to point to the source ; ship's data block TXA ; Set Y = X * 2 so it can act as an index into the ASL A ; two-byte lookup table at UNIV, which contains the TAY ; addresses of the ship data blocks. In this case we are ; multiplying X by 2, and X contains the source ship's ; slot number so Y is now an index for the source ship's ; entry in UNIV LDA UNIV,Y ; Set SC(1 0) to the address of the data block for the STA SC ; source ship LDA UNIV+1,Y STA SC+1 ; We have now set up our variables as follows: ; ; SC(1 0) points to the source's ship data block ; ; INF(1 0) points to the destination's ship data block ; ; P(1 0) points to the destination's line heap ; ; so let's start copying data from the source to the ; destination LDY #36 ; We are going to be using Y as a counter for the 37 ; bytes of ship data we want to copy from the source ; to the destination, so we set it to 36 to start things ; off, and will decrement Y for each byte we copy LDA (SC),Y ; Fetch byte #36 of the source's ship data block at SC, STA (INF),Y ; and store it in byte #36 of the destination's block DEY ; at INF, so that's the ship's NEWB flags copied from ; the source to the destination. One down, quite a few ; to go... LDA (SC),Y ; Fetch byte #35 of the source's ship data block at SC, STA (INF),Y ; and store it in byte #35 of the destination's block ; at INF, so that's the ship's energy copied from the ; source to the destination DEY ; Fetch byte #34 of the source ship, which is the LDA (SC),Y ; high byte of the source ship's line heap, and store STA K+1 ; in K+1 LDA P+1 ; Set the low byte of the destination's heap pointer STA (INF),Y ; to P+1 DEY ; Fetch byte #33 of the source ship, which is the LDA (SC),Y ; low byte of the source ship's heap, and store in K STA K ; so now we have the following: ; ; K(1 0) points to the source's line heap LDA P ; Set the low byte of the destination's heap pointer STA (INF),Y ; to P, so now the destination's heap pointer is to ; P(1 0), so that's the heap pointer in bytes #33 and ; #34 done DEY ; Luckily, we can just copy the rest of the source's ; ship data block into the destination, as there are no ; more address pointers, so first we decrement our ; counter in Y to point to the next byte (the AI flag) ; in byte #32) and then start looping .KSL2 ;DEY ; This instruction is commented out in the original ; source LDA (SC),Y ; Copy the Y-th byte of the source to the Y-th byte of STA (INF),Y ; the destination ;TYA ; This instruction is commented out in the original ; source DEY ; Decrement the counter BPL KSL2 ; Loop back to KSL2 to copy the next byte until we have ; copied the whole block ; We have now shuffled the ship's slot and the ship's ; data block, so we only have the heap data itself to do LDA SC ; First, we copy SC into INF, so when we loop round STA INF ; again, INF will correctly point to the destination for LDA SC+1 ; the next iteration STA INF+1 LDY T ; Now we want to move the contents of the heap, as all ; we did above was to update the pointers, so first ; we set a counter in Y that is initially set to T ; (which we set above to the maximum heap size for the ; source ship) ; ; As a reminder, we have already set the following: ; ; K(1 0) points to the source's line heap ; ; P(1 0) points to the destination's line heap ; ; so we can move the heap data by simply copying the ; correct number of bytes from K(1 0) to P(1 0) .KSL3 DEY ; Decrement the counter LDA (K),Y ; Copy the Y-th byte of the source heap at K(1 0) to STA (P),Y ; the destination heap at P(1 0) TYA ; Loop back to KSL3 to copy the next byte, until we BNE KSL3 ; have done them all BEQ KSL1 ; We have now shuffled everything down one slot, so ; jump back up to KSL1 to see if there is another slot ; that needs shuffling down (this BEQ is effectively a ; JMP as A will always be zero) ; ****************************************************************************** ; ; Name: THERE ; Type: Subroutine ; Category: Missions ; Summary: Check whether we are in the Constrictor's system in mission 1 ; ; ------------------------------------------------------------------------------ ; ; The stolen Constrictor is the target of mission 1. We finally track it down to ; the Orarra system in the second galaxy, which is at galactic coordinates ; (144, 33). This routine checks whether we are in this system and sets the C ; flag accordingly. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if we are in the Constrictor system, otherwise clear ; ; ****************************************************************************** .THERE LDX GCNT ; Set X = GCNT - 1 DEX BNE THEX ; If X is non-zero (i.e. GCNT is not 1, so we are not in ; the second galaxy), then jump to THEX LDA QQ0 ; Set A = the current system's galactic x-coordinate CMP #144 ; If A <> 144 then jump to THEX BNE THEX LDA QQ1 ; Set A = the current system's galactic y-coordinate CMP #33 ; If A = 33 then set the C flag BEQ THEX+1 ; If A = 33 then jump to THEX+1, so we return from the ; subroutine with the C flag set (otherwise we clear the ; C flag with the next instruction) .THEX CLC ; Clear the C flag RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: RESET ; Type: Subroutine ; Category: Start and end ; Summary: Reset most variables ; ; ------------------------------------------------------------------------------ ; ; Reset our ship and various controls, recharge shields and energy, and then ; fall through into RES2 to reset the stardust and the ship workspace at INWK. ; ; In this subroutine, this means zero-filling the following locations: ; ; * Pages $9, $A, $B, $C and $D ; ; * BETA to BETA+6, which covers the following: ; ; * BETA, BET1 - Set pitch to 0 ; ; * XC, YC - Set text cursor to (0, 0) ; ; * QQ22 - Set hyperspace counters to 0 ; ; * ECMA - Turn E.C.M. off ; ; It also sets QQ12 to $FF, to indicate we are docked, recharges the shields and ; energy banks, and then falls through into RES2. ; ; ****************************************************************************** .RESET JSR ZERO ; Reset the ship slots for the local bubble of universe, ; and various flight and ship status variables LDX #6 ; Set up a counter for zeroing BETA through BETA+6 .SAL3 STA BETA,X ; Zero the X-th byte after BETA DEX ; Decrement the loop counter BPL SAL3 ; Loop back for the next byte to zero TXA ; X is now negative - i.e. $FF - so this sets A and QQ12 STA QQ12 ; to $FF to indicate we are docked LDX #2 ; We're now going to recharge both shields and the ; energy bank, which live in the three bytes at FSH, ; ASH (FSH+1) and ENERGY (FSH+2), so set a loop counter ; in X for 3 bytes .REL5 STA FSH,X ; Set the X-th byte of FSH to $FF to charge up that ; shield/bank DEX ; Decrement the loop counter BPL REL5 ; Loop back to REL5 until we have recharged both shields ; and the energy bank ; Fall through into RES2 to reset the stardust and ship ; workspace at INWK ; ****************************************************************************** ; ; Name: RES2 ; Type: Subroutine ; Category: Start and end ; Summary: Reset a number of flight variables and workspaces ; ; ------------------------------------------------------------------------------ ; ; This is called after we launch from a space station, arrive in a new system ; after hyperspace, launch an escape pod, or die a cold, lonely death in the ; depths of space. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Y Y is set to $FF ; ; ****************************************************************************** .RES2 JSR stopbd ; Stop playing the docking music (if it is playing) LDA BOMB ; If the energy bomb has been set off, then BOMB will be BPL BOMBOK ; negative, so this skips the following instructions if ; our energy bomb is not going off JSR BOMBOFF ; Switch off the energy bomb effect STA BOMB ; The call to BOMBOFF sets A = 0, so this zeroes BOMB to ; switch off the energy bomb explosion .BOMBOK LDA #NOST ; Reset NOSTM, the number of stardust particles, to the STA NOSTM ; maximum allowed (20) LDX #$FF ; Reset LSX2 and LSY2, the ball line heaps used by the STX LSX2 ; BLINE routine for drawing circles, to $FF, to set the STX LSY2 ; heap to empty STX MSTG ; Reset MSTG, the missile target, to $FF (no target) LDA #128 ; Set the current pitch rate to the mid-point, 128 STA JSTY STA ALP2 ; Reset ALP2 (roll sign) and BET2 (pitch sign) STA BET2 ; to negative, i.e. pitch and roll negative ASL A ; This sets A to 0 STA BETA ; Reset BETA (pitch angle alpha) to 0 STA BET1 ; Reset BET1 (magnitude of the pitch angle) to 0 STA ALP2+1 ; Reset ALP2+1 (flipped roll sign) and BET2+1 (flipped STA BET2+1 ; pitch sign) to positive, i.e. pitch and roll negative STA MCNT ; Reset MCNT (the main loop counter) to 0 STA TRIBCT ; Reset TRIBCT (the Trumbles counter) to 0 LDA #3 ; Reset DELTA (speed) to 3 STA DELTA STA ALPHA ; Reset ALPHA (roll angle alpha) to 3 STA ALP1 ; Reset ALP1 (magnitude of roll angle alpha) to 3 LDA #$10 ; Switch the text colour to white STA COL2 LDA #0 ; Set dontclip to 0 to enable line-clipping in the LL145 STA dontclip ; routine, as we only disable this for the Short-range ; Chart LDA #2*Y-1 ; Set Yx2M1 to the number of pixel lines in the space STA Yx2M1 ; view LDA SSPR ; Fetch the "space station present" flag, and if we are BEQ P%+5 ; not inside the safe zone, skip the next instruction JSR SPBLB ; Light up the space station bulb on the dashboard LDA ECMA ; Fetch the E.C.M. status flag, and if E.C.M. is off, BEQ yu ; skip the next instruction JSR ECMOF ; Turn off the E.C.M. sound .yu JSR WPSHPS ; Wipe all ships from the scanner JSR ZERO ; Reset the ship slots for the local bubble of universe, ; and various flight and ship status variables LDA #LO(LS%) ; We have reset the ship line heap, so we now point STA SLSP ; SLSP to LS% (the byte below the ship blueprints at D%) LDA #HI(LS%) ; to indicate that the heap is empty STA SLSP+1 ; Finally, fall through into ZINF to reset the INWK ; ship workspace ; ****************************************************************************** ; ; Name: ZINF ; Type: Subroutine ; Category: Universe ; Summary: Reset the INWK workspace and orientation vectors ; Deep dive: Orientation vectors ; ; ------------------------------------------------------------------------------ ; ; Zero-fill the INWK ship workspace and reset the orientation vectors, with ; nosev pointing out of the screen, towards us. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Y Y is set to $FF ; ; ****************************************************************************** .ZINF LDY #NI%-1 ; There are NI% bytes in the INWK workspace, so set a ; counter in Y so we can loop through them LDA #0 ; Set A to 0 so we can zero-fill the workspace .ZI1 STA INWK,Y ; Zero the Y-th byte of the INWK workspace DEY ; Decrement the loop counter BPL ZI1 ; Loop back for the next byte, ending when we have ; zero-filled the last byte at INWK, which leaves Y ; with a value of $FF ; Finally, we reset the orientation vectors as follows: ; ; sidev = (1, 0, 0) ; roofv = (0, 1, 0) ; nosev = (0, 0, -1) ; ; 96 * 256 ($6000) represents 1 in the orientation ; vectors, while -96 * 256 ($E000) represents -1. We ; already set the vectors to zero above, so we just ; need to set up the high bytes of the diagonal values ; and we're done. The negative nosev makes the ship ; point towards us, as the z-axis points into the screen LDA #96 ; Set A to represent a 1 (in vector terms) STA INWK+18 ; Set byte #18 = roofv_y_hi = 96 = 1 STA INWK+22 ; Set byte #22 = sidev_x_hi = 96 = 1 ORA #%10000000 ; Flip the sign of A to represent a -1 STA INWK+14 ; Set byte #14 = nosev_z_hi = -96 = -1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: msblob ; Type: Subroutine ; Category: Dashboard ; Summary: Display the dashboard's missile indicators in green ; ; ------------------------------------------------------------------------------ ; ; Display the dashboard's missile indicators, with all the missiles reset to ; green (i.e. not armed or locked). ; ; ****************************************************************************** .msblob LDX #4 ; Set up a loop counter in X to count through all four ; missile indicators .ss CPX NOMSL ; If the counter is equal to the number of missiles, BEQ SAL8 ; jump down to SAL8 to draw the remaining missiles, as ; the rest of them are present and should be drawn in ; green LDY #BLACK2 ; Draw the missile indicator at position X in black JSR MSBAR DEX ; Decrement the counter to point to the next missile BNE ss ; Loop back to ss if we still have missiles to draw RTS ; Return from the subroutine .SAL8 LDY #GREEN2 ; Draw the missile indicator at position X in green JSR MSBAR DEX ; Decrement the counter to point to the next missile BNE SAL8 ; Loop back to SAL8 if we still have missiles to draw RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: me2 ; Type: Subroutine ; Category: Flight ; Summary: Remove an in-flight message from the space view ; ; ****************************************************************************** .me2 LDA QQ11 ; If this is not the space view, jump down to clynsneed BNE clynsneed ; to skip displaying the in-flight message LDA MCH ; Fetch the token number of the current message into A JSR MESS ; Call MESS to print the token, which will remove it ; from the screen as printing uses EOR logic LDA #0 ; Set the delay in DLY to 0, so any new in-flight STA DLY ; messages will be shown instantly JMP me3 ; Jump back into the main spawning loop at me3 .clynsneed JSR CLYNS ; Clear the bottom three text rows of the upper screen, ; and move the text cursor to the first cleared row JMP me3 ; Jump back into the main spawning loop at me3 ; ****************************************************************************** ; ; Name: Ze ; Type: Subroutine ; Category: Universe ; Summary: Initialise the INWK workspace to a fairly aggressive ship ; Deep dive: Fixing ship positions ; Aggression and hostility in ship tactics ; ; ------------------------------------------------------------------------------ ; ; Specifically, this routine does the following: ; ; * Reset the INWK ship workspace ; ; * Set the ship to a fair distance away in all axes, in front of us but ; randomly up or down, left or right ; ; * Give the ship a 4% chance of having E.C.M. ; ; * Set the ship's aggression level to at least 32 out of 63, with AI enabled ; ; This routine also sets A, X, T1 and the C flag to random values. ; ; Note that because this routine uses the value of X returned by DORND, and X ; contains the value of A returned by the previous call to DORND, this routine ; does not necessarily set the new ship to a totally random location. ; ; ****************************************************************************** .Ze JSR ZINF ; Call ZINF to reset the INWK ship workspace JSR DORND ; Set A and X to random numbers STA T1 ; Store A in T1 AND #%10000000 ; Extract the sign of A and store in x_sign STA INWK+2 TXA ; Extract the sign of X and store in y_sign AND #%10000000 STA INWK+5 LDA #25 ; Set x_hi = y_hi = z_hi = 25, a fair distance away STA INWK+1 STA INWK+4 STA INWK+7 TXA ; Set the C flag if X >= 245 (4% chance) CMP #245 ROL A ; Set bit 0 of A to the C flag (i.e. there's a 4% ; chance of this ship having E.C.M.) ORA #%11000000 ; Set bits 6 and 7 of A, so the ship has AI (bit 7) and ; an aggression level of at least 32 out of 63 STA INWK+32 ; Store A in the AI flag of this ship ; Fall through into DORND2 to set A, X and the C flag ; randomly ; ****************************************************************************** ; ; Name: DORND ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Generate random numbers ; Deep dive: Generating random numbers ; Fixing ship positions ; ; ------------------------------------------------------------------------------ ; ; Set A and X to random numbers (though note that X is set to the random number ; that was returned in A the last time DORND was called). ; ; The C and V flags are also set randomly. ; ; If we want to generate a repeatable sequence of random numbers, when ; generating explosion clouds, for example, then we call DORND2 to ensure that ; the value of the C flag on entry doesn't affect the outcome, as otherwise we ; might not get the same sequence of numbers if the C flag changes. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; DORND2 Make sure the C flag doesn't affect the outcome ; ; ****************************************************************************** .DORND2 CLC ; Clear the C flag so the value of the C flag on entry ; doesn't affect the outcome .DORND LDA RAND ; Calculate the next two values f2 and f3 in the feeder ROL A ; sequence: TAX ; ADC RAND+2 ; * f2 = (f1 << 1) mod 256 + C flag on entry STA RAND ; * f3 = f0 + f2 + (1 if bit 7 of f1 is set) STX RAND+2 ; * C flag is set according to the f3 calculation LDA RAND+1 ; Calculate the next value m2 in the main sequence: TAX ; ADC RAND+3 ; * A = m2 = m0 + m1 + C flag from feeder calculation STA RAND+1 ; * X = m1 STX RAND+3 ; * C and V flags set according to the m2 calculation RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: Main game loop (Part 1 of 6) ; Type: Subroutine ; Category: Main loop ; Summary: Spawn a trader (a Cobra Mk III, Python, Boa or Anaconda) ; Deep dive: Program flow of the main game loop ; Ship data blocks ; Aggression and hostility in ship tactics ; ; ------------------------------------------------------------------------------ ; ; This is part of the main game loop. This is where the core loop of the game ; lives, and it's in two parts. The shorter loop (just parts 5 and 6) is ; iterated when we are docked, while the entire loop from part 1 to 6 iterates ; if we are in space. ; ; This section covers the following: ; ; * Spawn a trader, i.e. a Cobra Mk III, Python, Boa or Anaconda, with a 50% ; chance of it having an E.C.M., a 50% chance of it docking, a random ; aggression level, a speed between 16 and 31, and a gentle clockwise roll ; ; We call this from within the main loop. ; ; ****************************************************************************** .MTT4 JSR DORND ; Set A and X to random numbers LSR A ; Clear bit 7 of our random number in A and set the C ; flag to bit 0 of A, which is random STA INWK+32 ; Store this in the ship's AI flag, so this ship does ; not have AI STA INWK+29 ; Store A in the ship's roll counter, giving it a ; clockwise roll (as bit 7 is clear), and a 1 in 127 ; chance of it having no damping ROL INWK+31 ; This instruction would appear to set bit 0 of the ; ship's missile count randomly (as the C flag was set), ; giving the ship either no missiles or one missile ; ; However, INWK+31 is overwritten in the call to the ; NWSHP routine below, where it is set to the number of ; missiles from the ship blueprint, and the value of the ; C flag is not used, so this instruction actually has ; no effect ; ; Interestingly, the original source code for the NWSPS ; routine also has an instruction that sets INWK+31 and ; which gets overwritten when it falls through into ; NWSHP, but in this case the instruction is commented ; out in the source. Perhaps the original version of ; NWSHP didn't set the missile count and instead relied ; on the calling code to set it, and when the authors ; changed it, they commented out the INWK+31 instruction ; in NWSPS and forgot about this one. Who knows? AND #31 ; Set the ship speed to our random number, set to a ORA #16 ; minimum of 16 and a maximum of 31 STA INWK+27 JSR DORND ; Set A and X to random numbers, plus the C flag BMI nodo ; If A is negative (50% chance), jump to nodo to skip ; the following ; If we get here then we are going to spawn a ship that ; is minding its own business and trying to dock LDA INWK+32 ; Set bits 6 and 7 of A, so the ship has AI (bit 7) and ORA #%11000000 ; an aggression level of at least 32 out of 63 (this STA INWK+32 ; makes the ship more likely to turn towards its target, ; which in this case is the space station, as we are ; about to set the ship flags so it is docking) LDX #%00010000 ; Set bit 4 of the ship's NEWB flags, to indicate that STX NEWB ; this ship is docking .nodo AND #2 ; This reduces A to a random value of either 0 or 2 ADC #CYL ; Set A = A + C + #CYL ; ; where A is 0 or 2 and C is 0 or 1, so this gives us a ; ship type from the following: Cobra Mk III, Python, ; Boa or Anaconda CMP #HER ; If A is now the ship type of a rock hermit, jump to BEQ TT100 ; TT100 to skip the following instruction JSR NWSHP ; Add a new ship of type A to the local bubble and fall ; through into the main game loop again ; ****************************************************************************** ; ; Name: Main game loop (Part 2 of 6) ; Type: Subroutine ; Category: Main loop ; Summary: Call the main flight loop, and potentially spawn a trader, an ; asteroid, or a cargo canister ; Deep dive: Program flow of the main game loop ; Ship data blocks ; Fixing ship positions ; ; ------------------------------------------------------------------------------ ; ; This section covers the following: ; ; * Call M% to do the main flight loop ; ; * Potentially spawn a trader, asteroid or cargo canister ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; TT100 The entry point for the start of the main game loop, ; which calls the main flight loop and the moves into the ; spawning routine ; ; me3 Used by me2 to jump back into the main game loop after ; printing an in-flight message ; ; ****************************************************************************** .TT100 JSR M% ; Call M% to iterate through the main flight loop DEC DLY ; Decrement the delay counter in DLY, so any in-flight ; messages get removed once the counter reaches zero BEQ me2 ; If DLY is now 0, jump to me2 to remove any in-flight ; message from the space view, and once done, return to ; me3 below, skipping the following two instructions BPL me3 ; If DLY is positive, jump to me3 to skip the next ; instruction INC DLY ; If we get here, DLY is negative, so we have gone too ; and need to increment DLY back to 0 .me3 DEC MCNT ; Decrement the main loop counter in MCNT BEQ P%+5 ; If the counter has reached zero, which it will do ; every 256 main loops, skip the next JMP instruction ; (or to put it another way, if the counter hasn't ; reached zero, jump down to MLOOP, skipping all the ; following checks) .ytq JMP MLOOP ; Jump down to MLOOP to do some end-of-loop tidying and ; restart the main loop ; We only get here once every 256 iterations of the ; main loop. If we aren't in witchspace and don't ; already have 3 or more asteroids in our local bubble, ; then this section has a 13% chance of spawning ; something benign (the other 87% of the time we jump ; down to consider spawning cops, pirates and bounty ; hunters) ; ; If we are in that 13%, then 50% of the time this will ; be a trader, and the other 50% of the time it will ; either be an asteroid (98.5% chance) or, very rarely, ; a cargo canister (1.5% chance) LDA MJ ; If we are in witchspace following a mis-jump, skip the BNE ytq ; following by jumping down to MLOOP (via ytq above) JSR DORND ; Set A and X to random numbers CMP #35 ; If A >= 35 (87% chance), jump down to MTT1 to skip BCS MTT1 ; the spawning of an asteroid or cargo canister and ; potentially spawn something else LDA JUNK ; If we already have 3 or more bits of junk in the local CMP #3 ; bubble, jump down to MTT1 to skip the following and BCS MTT1 ; potentially spawn something else JSR ZINF ; Call ZINF to reset the INWK ship workspace LDA #38 ; Set z_hi = 38 (far away) STA INWK+7 JSR DORND ; Set A, X and C flag to random numbers STA INWK ; Set x_lo = random STX INWK+3 ; Set y_lo = random ; ; Note that because we use the value of X returned by ; DORND, and X contains the value of A returned by the ; previous call to DORND, this does not set the new ship ; to a totally random location AND #%10000000 ; Set x_sign = bit 7 of x_lo STA INWK+2 TXA ; Set y_sign = bit 7 of y_lo AND #%10000000 STA INWK+5 ROL INWK+1 ; Set bit 1 of x_hi to the C flag, which is random, so ROL INWK+1 ; this randomly moves us off-centre by 512 (as if x_hi ; is %00000010, then (x_hi x_lo) is 512 + x_lo) JSR DORND ; Set A, X and V flag to random numbers BVS MTT4 ; If V flag is set (50% chance), jump up to MTT4 to ; spawn a trader ORA #%01101111 ; Take the random number in A and set bits 0-3 and 5-6, STA INWK+29 ; so the result has a 50% chance of being positive or ; negative, and a 50% chance of bits 0-6 being 127. ; Storing this number in the roll counter therefore ; gives our new ship a fast roll speed with a 50% ; chance of having no damping, plus a 50% chance of ; rolling clockwise or anti-clockwise LDA SSPR ; If we are inside the space station safe zone, jump BNE MTT1 ; down to MTT1 to skip the following and potentially ; spawn something else TXA ; Set A to the random X we set above, which we haven't BCS MTT2 ; used yet, and if the C flag is set (50% chance) jump ; down to MTT2 to skip the following AND #31 ; Set the ship speed to our random number, set to a ORA #16 ; minimum of 16 and a maximum of 31 STA INWK+27 BCC MTT3 ; Jump down to MTT3, skipping the following (this BCC ; is effectively a JMP as we know the C flag is clear, ; having passed through the BCS above) .MTT2 ORA #%01111111 ; Set bits 0-6 of A to 127, leaving bit 7 as random, so STA INWK+30 ; storing this number in the pitch counter means we have ; full pitch with no damping, with a 50% chance of ; pitching up or down .MTT3 JSR DORND ; Set A and X to random numbers CMP #252 ; If random A < 252 (98.8% of the time), jump to thongs BCC thongs ; to skip the following LDA #HER ; Set A to #HER so we spawn a rock hermit 1.2% of the ; time STA INWK+32 ; Set byte #32 to %00001111 to give the rock hermit an ; E.C.M. BNE whips ; Jump to whips (this BNE is effectively a JMP as A will ; never be zero) .thongs CMP #10 ; If random A >= 10 (96% of the time), set the C flag AND #1 ; Reduce A to a random number that's 0 or 1 ADC #OIL ; Set A = #OIL + A + C, so there's a 2% chance of us ; spawning a cargo canister (#OIL), a 50% chance of ; us spawning a boulder (#OIL + 1), and a 48% chance of ; us spawning an asteroid (#OIL + 2) .whips JSR NWSHP ; Add our new asteroid or canister to the universe ; ****************************************************************************** ; ; Name: Main game loop (Part 3 of 6) ; Type: Subroutine ; Category: Main loop ; Summary: Potentially spawn a cop, particularly if we've been bad ; Deep dive: Program flow of the main game loop ; Ship data blocks ; Fixing ship positions ; ; ------------------------------------------------------------------------------ ; ; This section covers the following: ; ; * Potentially spawn a cop (in a Viper), very rarely if we have been good, ; more often if have been naughty, and very often if we have been properly ; bad ; ; * Very rarely, consider spawning a Thargoid, or vanishingly rarely, a Cougar ; ; ****************************************************************************** .MTT1 LDA SSPR ; If we are outside the space station's safe zone, skip BEQ P%+5 ; the following instruction .MLOOPS JMP MLOOP ; Jump to MLOOP to skip the following JSR BAD ; Call BAD to work out how much illegal contraband we ; are carrying in our hold (A is up to 40 for a ; standard hold crammed with contraband, up to 70 for ; an extended cargo hold full of narcotics and slaves) ASL A ; Double A to a maximum of 80 or 140 LDX MANY+COPS ; If there are no cops in the local bubble, skip the BEQ P%+5 ; next instruction ORA FIST ; There are cops in the vicinity and we've got a hold ; full of jail time, so OR the value in A with FIST to ; get a new value that is at least as high as both ; values, to reflect the fact that they have almost ; certainly scanned our ship STA T ; Store our badness level in T JSR Ze ; Call Ze to initialise INWK to a fairly aggressive ; ship, and set A and X to random values ; ; Note that because Ze uses the value of X returned by ; DORND, and X contains the value of A returned by the ; previous call to DORND, this does not set the new ship ; to a totally random location CMP #136 ; If the random number in A = 136 (0.4% chance), jump BEQ fothg ; to fothg in part 4 to spawn either a Thargoid or, very ; rarely, a Cougar CMP T ; If the random value in A >= our badness level, which BCS P%+7 ; will be the case unless we have been really, really ; bad, then skip the following two instructions (so ; if we are really bad, there's a higher chance of ; spawning a cop, otherwise we got away with it, for ; now) LDA #COPS ; Add a new police ship to the local bubble JSR NWSHP LDA MANY+COPS ; If we now have at least one cop in the local bubble, BNE MLOOPS ; jump down to MLOOPS to stop spawning, otherwise fall ; through into the next part to look at spawning ; something else ; ****************************************************************************** ; ; Name: Main game loop (Part 4 of 6) ; Type: Subroutine ; Category: Main loop ; Summary: Potentially spawn a lone bounty hunter, a Thargoid, or up to four ; pirates ; Deep dive: Program flow of the main game loop ; Ship data blocks ; Fixing ship positions ; The elusive Cougar ; Aggression and hostility in ship tactics ; ; ------------------------------------------------------------------------------ ; ; This section covers the following: ; ; * Potentially spawn (35% chance) either a lone bounty hunter (a Cobra Mk ; III, Asp Mk II, Python or Fer-de-lance), a Thargoid, or a group of up to 4 ; pirates (a mix of Sidewinders, Mambas, Kraits, Adders, Geckos, Cobras Mk I ; and III, and Worms) ; ; * Also potentially spawn a Constrictor if this is the mission 1 endgame, or ; Thargoids if mission 2 is in progress ; ; ****************************************************************************** DEC EV ; Decrement EV, the extra vessels spawning delay, and if BPL MLOOPS ; it is still positive, jump to MLOOPS to stop spawning, ; so we only do the following when the EV counter runs ; down INC EV ; EV is negative, so bump it up again, setting it back ; to 0 LDA TP ; Fetch bits 2 and 3 of TP, which contain the status of AND #%00001100 ; mission 2 CMP #%00001000 ; If bit 3 is set and bit 2 is clear, keep going to BNE nopl ; spawn a Thargoid as we are transporting the plans in ; mission 2 and the Thargoids are trying to stop us, ; otherwise jump to nopl to skip spawning a Thargoid JSR DORND ; Set A and X to random numbers CMP #200 ; If the random number in A < 200 (78% chance), jump to BCC nopl ; nopl to skip spawning a Thargoid .fothg2 JSR GTHG ; Call GTHG to spawn a Thargoid ship and a Thargon ; companion .nopl JSR DORND ; Set A and X to random numbers LDY gov ; If the government of this system is 0 (anarchy), jump BEQ LABEL_2 ; straight to LABEL_2 to start spawning pirates or a ; lone bounty hunter CMP #90 ; If the random number in A >= 90 (65% chance), jump to BCS MLOOPS ; MLOOPS to stop spawning (so there's a 35% chance of ; spawning pirates or a lone bounty hunter) AND #7 ; Reduce the random number in A to the range 0-7, and CMP gov ; if A is less than government of this system, jump BCC MLOOPS ; to MLOOPS to stop spawning (so safer governments with ; larger gov numbers have a greater chance of jumping ; out, which is another way of saying that more ; dangerous systems spawn pirates and bounty hunters ; more often) .LABEL_2 ; Now to spawn a lone bounty hunter, a Thargoid or a ; group of pirates JSR Ze ; Call Ze to initialise INWK to a fairly aggressive ; ship, and set A and X to random values ; ; Note that because Ze uses the value of X returned by ; DORND, and X contains the value of A returned by the ; previous call to DORND, this does not set the new ship ; to a totally random location CMP #100 ; If the random number in A >= 100 (61% chance), jump BCS mt1 ; to mt1 to spawn pirates, otherwise keep going to ; spawn a lone bounty hunter or a Thargoid INC EV ; Increase the extra vessels spawning counter, to ; prevent the next attempt to spawn extra vessels AND #3 ; Set A = random number in the range 0-3, which we ; will now use to determine the type of ship ADC #CYL2 ; Add A to #CYL2 (we know the C flag is clear as we ; passed through the BCS above), so A is now one of the ; lone bounty hunter ships, i.e. Cobra Mk III (pirate), ; Asp Mk II, Python (pirate) or Fer-de-lance ; ; Interestingly, this logic means that the Moray, which ; is the ship after the Fer-de-lance in the XX21 table, ; never spawns, as the above logic chooses a blueprint ; number in the range CYL2 to CYL2+3 (i.e. 24 to 27), ; and the Moray is blueprint 28 ; ; No other code spawns the ship with blueprint 28, so ; this means the Moray is never seen in Elite ; ; This is presumably a bug, which could be very easily ; fixed by inserting one of the following instructions ; before the ADC #CYL2 instruction above: ; ; * SEC would change the range to 25 to 28, which ; would cover the Asp Mk II, Python (pirate), ; Fer-de-lance and Moray ; ; * LSR A would set the C flag to a random number to ; give a range of 24 to 28, which would cover the ; Cobra Mk III (pirate), Asp Mk II, Python (pirate), ; Fer-de-lance and Moray ; ; It's hard to know what the authors' original intent ; was, but the second approach makes the Moray and Cobra ; Mk III the rarest choices, with the Asp Mk II, Python ; and Fer-de-Lance being more likely, and as the Moray ; is described in the literature as a rare ship, and the ; Cobra can already be spawned as part of a group of ; pirates (see mt1 below), I tend to favour the LSR A ; solution over the SEC approach TAY ; Copy the new ship type to Y JSR THERE ; Call THERE to see if we are in the Constrictor's ; system in mission 1 BCC NOCON ; If the C flag is clear then we are not in the ; Constrictor's system, so skip to NOCON LDA #%11111001 ; Set the AI flag of this ship so that it has E.C.M., STA INWK+32 ; has a very high aggression level of 60 out of 63, is ; hostile, and has AI enabled - nasty stuff! LDA TP ; Fetch bits 0 and 1 of TP, which contain the status of AND #%00000011 ; mission 1 LSR A ; Shift bit 0 into the C flag BCC NOCON ; If bit 0 is clear, skip to NOCON as mission 1 is not ; in progress ORA MANY+CON ; Bit 0 of A now contains bit 1 of TP, so this will be ; set if we have already completed mission 1, so this OR ; will be non-zero if we have either completed mission ; 1, or there is already a Constrictor in our local ; bubble of universe (in which case MANY+CON will be ; non-zero) BEQ YESCON ; If A = 0 then mission 1 is in progress, we haven't ; completed it yet, and there is no Constrictor in the ; vicinity, so jump to YESCON to spawn the Constrictor .NOCON LDA #%00000100 ; Set bit 2 of the NEWB flags and clear all other bits, STA NEWB ; so the ship we are about to spawn is hostile ; We now build the AI flag for this ship in A JSR DORND ; Set A and X to random numbers CMP #200 ; First, set the C flag if X >= 200 (22% chance) ROL A ; Set bit 0 of A to the C flag (i.e. there's a 22% ; chance of this ship having E.C.M.) ORA #%11000000 ; Set bits 6 and 7 of A, so the ship has AI (bit 7) and ; an aggression level of at least 32 out of 63 STA INWK+32 ; Store A in the AI flag of this ship TYA ; Set A to the new ship type in Y EQUB $2C ; Skip the next instruction by turning it into ; $2C $A9 $1F, or BIT $1FA9, which does nothing apart ; from affect the flags .YESCON LDA #CON ; If we jump straight here, we are in the mission 1 ; endgame and it's time to spawn the Constrictor, so ; set A to the Constrictor's type .focoug JSR NWSHP ; Spawn the new ship, whether it's a pirate, Thargoid, ; Cougar or Constrictor .mj1 JMP MLOOP ; Jump down to MLOOP, as we are done spawning ships .fothg LDA K%+6 ; Fetch the z_lo coordinate of the first ship in the K% AND #%00111110 ; block (i.e. the planet) and extract bits 1-5 BNE fothg2 ; If any of bits 1-5 are set (96.8% chance), jump up to ; fothg2 to spawn a Thargoid ; If we get here then we're going to spawn a Cougar, a ; very rare event indeed. How rare? Well, all the ; following have to happen in sequence: ; ; * Main loop iteration = 0 (1 in 256 iterations) ; * Skip asteroid spawning (87% chance) ; * Skip cop spawning (0.4% chance) ; * Skip Thargoid spawning (3.2% chance) ; ; so the chances of spawning a Cougar on any single main ; loop iteration are slim, to say the least LDA #18 ; Give the ship we're about to spawn a speed of 27 STA INWK+27 LDA #%01111001 ; Give it an E.C.M. and an aggression level of 60 out of STA INWK+32 ; 63, but don't enable its AI, so the ship will sit ; still in space unless it is hit, at which point it ; will defend itself vigorously ; ; This ensures the Cougar behaves like a ship with a ; cloaking device that hides it from the scanner, so it ; minds its own business until it's discovered LDA #COU ; Set the ship type to a Cougar and jump up to focoug BNE focoug ; to spawn it .mt1 AND #3 ; It's time to spawn a group of pirates, so set A to a ; random number in the range 0-3, which will be the ; loop counter for spawning pirates below (so we will ; spawn 1-4 pirates) STA EV ; Delay further spawnings by this number STA XX13 ; Store the number in XX13, the pirate counter .mt3 JSR DORND ; Set A and X to random numbers STA T ; Set T to a random number JSR DORND ; Set A and X to random numbers AND T ; Set A to the AND of two random numbers, so each bit ; has 25% chance of being set which makes the chances ; of a smaller number higher AND #7 ; Reduce A to a random number in the range 0-7, though ; with a bigger chance of a smaller number in this range ADC #PACK ; #PACK is set to #SH3, the ship type for a Sidewinder, ; so this sets our new ship type to one of the pack ; hunters, namely a Sidewinder, Mamba, Krait, Adder, ; Gecko, Cobra Mk I, Worm or Cobra Mk III (pirate) JSR NWSHP ; Try adding a new ship of type A to the local bubble DEC XX13 ; Decrement the pirate counter BPL mt3 ; If we need more pirates, loop back up to mt3, ; otherwise we are done spawning, so fall through into ; the end of the main loop at MLOOP ; ****************************************************************************** ; ; Name: Main game loop (Part 5 of 6) ; Type: Subroutine ; Category: Main loop ; Summary: Cool down lasers, make calls to update the dashboard ; Deep dive: Program flow of the main game loop ; The dashboard indicators ; The Trumbles mission ; ; ------------------------------------------------------------------------------ ; ; This is the first half of the minimal game loop, which we iterate when we are ; docked. This section covers the following: ; ; * Cool down lasers ; ; * Make calls to update the dashboard ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; MLOOP The entry point for the main game loop. This entry point ; comes after the call to the main flight loop and ; spawning routines, so it marks the start of the main ; game loop for when we are docked (as we don't need to ; call the main flight loop or spawning routines if we ; aren't in space) ; ; ****************************************************************************** .MLOOP LDX #$FF ; Set the stack pointer to $01FF, which is the standard TXS ; location for the 6502 stack, so this instruction ; effectively resets the stack LDX GNTMP ; If the laser temperature in GNTMP is non-zero, BEQ EE20 ; decrement it (i.e. cool it down a bit) DEC GNTMP .EE20 LDX LASCT ; Set X to the value of LASCT, the laser pulse count BEQ NOLASCT ; If X = 0 then jump to NOLASCT to skip reducing LASCT, ; as it can't be reduced any further DEX ; Decrement the value of LASCT in X BEQ P%+3 ; If X = 0, skip the next instruction DEX ; Decrement the value of LASCT in X again STX LASCT ; Store the decremented value of X in LASCT, so LASCT ; gets reduced by 2, but not into negative territory .NOLASCT LDA QQ11 ; If QQ11 is non-zero then this is not the space view, BNE P%+5 ; so skip the following instruction as only the space ; view has the dashboard JSR DIALS ; Call DIALS to update the dashboard LDA QQ11 ; If this is a space view, jump to plus13 to skip the BEQ plus13 ; following five instructions AND PATG ; If PATG = $FF (author names are shown on start-up) LSR A ; and bit 0 of QQ11 is 1 (the current view is type 1), BCS plus13 ; then skip the following two instructions LDY #2 ; Wait for 2/50 of a second (0.04 seconds) on PAL JSR DELAY ; systems, or 2/60 of a second (0.03 seconds) on NTSC, ; to slow the main loop down a bit .plus13 LDA TRIBBLE+1 ; If the high byte of TRIBBLE(1 0), the number of BEQ nobabies ; Trumbles in the hold, is zero, jump to nobabies to ; skip the following ; We have a lot of Trumbles in the hold, so let's see if ; any of them are breeding (note that Trumbles always ; breed when we jump into a new system in the SOLAR ; routine, but when we have lots of them, they also ; breed here in the main flight loop) JSR DORND ; Set A and X to random numbers CMP #220 ; If A >= 220 then set the C flag (14% chance) LDA TRIBBLE ; Add the C flag to TRIBBLE(1 0), starting with the low ADC #0 ; bytes STA TRIBBLE BCC nobabies ; And then the high bytes INC TRIBBLE+1 ; ; So there is a 14% chance of a Trumble being born BPL nobabies ; If the high byte of TRIBBLE(1 0) is now $80, then DEC TRIBBLE+1 ; decrement it back to $7F, so the number of Trumbles ; never goes above $7FFF (32767) .nobabies LDA TRIBBLE+1 ; If the high byte of TRIBBLE(1 0), the number of BEQ NOSQUEEK ; Trumbles in the hold, is zero, jump to NOSQUEEK to ; skip the following ; We have a lot of Trumbles in the hold, so they are ; probably making a bit of a noise STA T ; Store the high byte of the number of Trumbles in T LDA CABTMP ; If the cabin temperature is >= 224 then skip the ASL T CMP #224 ; instruction and leave the value of A as a lower value, BCS P%+4 ; so the chances of the Trumbles making a noise in hot ; temperatures is lessened (specifically, this is the ; temperature at which the fuel scoops start working) ASL T ; Set T = T * 2 JSR DORND ; Set A and X to random numbers CMP T ; If A >= T then jump to NOSQUEEK to skip making any BCS NOSQUEEK ; noise, so there is a higher chance of Trumbles making ; noise when there are lots of them and the cabin ; temperature is cool enough for the fuel scoops to be ; disabled (so they start to go quieter when things get ; too hot) ; If we get here then we want to make the noise of ; Trumbles living in our ship JSR DORND ; Set X to a random number in the range 64 to 255, which ORA #64 ; we will use as the frequency of the sound of Trumble TAX ; chatter (so they make a randomly pitched noise that's ; not too high) LDA #$80 ; Set A = $80 to pass to NOISE2 as the sustain volume ; and release length for when the cabin is relatively ; cool, so that's a sustain volume of 8 and a release ; length of 0 ; ; This makes the sounds more staccato and softer LDY CABTMP ; If the cabin temperature is < 224, jump to CPY #224 ; burnthebastards to make the noise of Trumbles lightly BCC burnthebastards ; toasting TXA ; Clip X to a random number in the range 0 to 15, so the AND #15 ; frequency of the Trumble chatter gets lower as the TAX ; cabin gets hotter LDA #$F1 ; Set A = $F1 to pass to NOISE2 as the sustain volume ; and release length for when the cabin is really hot, ; so that's a sustain volume of 15 and a release length ; of 1 ; ; This makes the sounds more drawn out and louder .burnthebastards LDY #sfxtrib ; Call the NOISE2 routine with Y = sfxtrib and A and X JSR NOISE2 ; set according to the cabin temperature: ; ; * A = $80, X = 64 to 255 when the cabin is cool ; (quieter, higher-pitched, more staccato squeaks) ; ; * A = $F1, X = 0 to 15 when the cabin is hot ; (louder, lower-pitched, more drawn out squeaks) ; ; This makes the sound of Trumbles either partying or ; or being slowly roasted .NOSQUEEK JSR TT17 ; Scan the keyboard for the cursor keys or joystick, ; returning the cursor's delta values in X and Y and ; the key pressed in A ; ****************************************************************************** ; ; Name: Main game loop (Part 6 of 6) ; Type: Subroutine ; Category: Main loop ; Summary: Process non-flight key presses (docked keys) ; Deep dive: Program flow of the main game loop ; ; ------------------------------------------------------------------------------ ; ; This is the second half of the minimal game loop, which we iterate when we are ; docked. This section covers the following: ; ; * Process more key presses (docked keys etc.) ; ; It also supports joining the main loop with a key already "pressed", so we can ; jump into the main game loop to perform a specific action. In practice, this ; is used when we enter the docking bay in BAY to display Status Mode, and when ; we finish buying or selling cargo in BAY2 to jump to the Inventory. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; FRCE The entry point for the main game loop if we want to ; jump straight to a specific screen, by pretending to ; "press" a key, in which case A contains the internal key ; number of the key we want to "press" ; ; ****************************************************************************** .FRCE JSR TT102 ; Call TT102 to process the key pressed in A LDA QQ12 ; Fetch the docked flag from QQ12 into A BEQ P%+5 ; If we are docked, loop back up to MLOOP just above JMP MLOOP ; to restart the main loop, but skipping all the flight ; and spawning code in the top part of the main loop JMP TT100 ; Otherwise jump to TT100 to restart the main loop from ; the start ; ****************************************************************************** ; ; Name: TT102 ; Type: Subroutine ; Category: Keyboard ; Summary: Process function key, save key, hyperspace and chart key presses ; and update the hyperspace counter ; ; ------------------------------------------------------------------------------ ; ; Process function key presses, plus "@" (save commander), "H" (hyperspace), ; "D" (show distance to system) and "O" (move chart cursor back to current ; system). We can also pass cursor position deltas in X and Y to indicate that ; the cursor keys or joystick have been used (i.e. the values that are returned ; by routine TT17). ; ; This routine also checks for the "F" key press (search for a system), which ; applies to enhanced versions only. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The key number of the key pressed ; ; X The amount to move the crosshairs in the x-axis ; ; Y The amount to move the crosshairs in the y-axis ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; T95 Print the distance to the selected system ; ; ****************************************************************************** .TT102 CMP #f8 ; If key "8" was pressed, jump to STATUS to show the BNE P%+5 ; Status Mode screen, returning from the subroutine JMP STATUS ; using a tail call CMP #f4 ; If key "4" was pressed, jump to TT22 to show the BNE P%+5 ; Long-range Chart, returning from the subroutine using JMP TT22 ; a tail call CMP #f5 ; If key "5" was pressed, jump to TT23 to show the BNE P%+5 ; Short-range Chart, returning from the subroutine using JMP TT23 ; a tail call CMP #f6 ; If key "6" was pressed, call TT111 to select the BNE TT92 ; system nearest to galactic coordinates (QQ9, QQ10) JSR TT111 ; (the location of the chart crosshairs) and set ZZ to JMP TT25 ; the system number, and then jump to TT25 to show the ; Data on System screen (along with an extended system ; description for the system in ZZ if we're docked), ; returning from the subroutine using a tail call .TT92 CMP #f9 ; If key "9" was pressed, jump to TT213 to show the BNE P%+5 ; Inventory screen, returning from the subroutine JMP TT213 ; using a tail call CMP #f7 ; If key "7" was pressed, jump to TT167 to show the BNE P%+5 ; Market Price screen, returning from the subroutine JMP TT167 ; using a tail call CMP #f0 ; If key F1 was pressed, jump to TT110 to launch our BNE fvw ; ship (if docked), returning from the subroutine using JMP TT110 ; a tail call .fvw BIT QQ12 ; If bit 7 of QQ12 is clear (i.e. we are not docked, but BPL INSP ; in space), jump to INSP to skip the following checks ; for f1-f3 and "@" (save commander file) key presses CMP #f3 ; If key "3" was pressed, jump to EQSHP to show the BNE P%+5 ; Equip Ship screen, returning from the subroutine using JMP EQSHP ; a tail call CMP #f1 ; If key "1" was pressed, jump to TT219 to show the BNE P%+5 ; Buy Cargo screen, returning from the subroutine using JMP TT219 ; a tail call CMP #$12 ; If "@" was not pressed, skip to nosave BNE nosave JSR SVE ; "@" was pressed, so call SVE to show the disk access ; menu BCC P%+5 ; If the C flag was set by SVE, then we loaded a new JMP QU5 ; commander file, so jump to QU5 to restart the game ; with the newly loaded commander JMP BAY ; Otherwise the C flag was clear, so jump to BAY to go ; to the docking bay (i.e. show the Status Mode screen) .nosave CMP #f2 ; If key "2" was pressed, jump to TT208 to show the BNE LABEL_3 ; Sell Cargo screen, returning from the subroutine using JMP TT208 ; a tail call .INSP CMP #f12 ; If key F3 was pressed, jump to chview1 BEQ chview1 CMP #f22 ; If key F5 was pressed, jump to chview2 BEQ chview2 CMP #f32 ; If key F7 was not pressed, jump to LABEL_3 to keep BNE LABEL_3 ; checking for which key was pressed LDX #3 ; Key F7 was pressed, so set the view number in X to ; 3 for the right view EQUB $2C ; Skip the next instruction by turning it into ; $2C $A2 $02, or BIT $02A2, which does nothing apart ; from affect the flags .chview2 LDX #2 ; If we jump to here, key F5 was pressed, so set the ; view number in X to 2 for the left view EQUB $2C ; Skip the next instruction by turning it into ; $2C $A2 $01, or BIT $02A2, which does nothing apart ; from affect the flags .chview1 LDX #1 ; If we jump to here, key F3 was pressed, so set the ; view number in X to 1 for the rear view JMP LOOK1 ; Jump to LOOK1 to switch to view X (rear, left or ; right), returning from the subroutine using a tail ; call .LABEL_3 BIT KLO+HINT ; If "H" was not pressed, skip the following instruction BPL P%+5 JMP hyp ; Jump to hyp to do a hyperspace jump (if we are in ; space), returning from the subroutine using a tail ; call .NWDAV5 CMP #DINT ; If "D" was pressed, jump to T95 to print the distance BEQ T95 ; to a system (if we are in one of the chart screens) CMP #FINT ; If "F" was not pressed, jump down to HME1, otherwise BNE HME1 ; keep going to process searching for systems LDA QQ12 ; If QQ12 = 0 (we are not docked), we can't search for BEQ t95 ; systems, so return from the subroutine (as t95 ; contains an RTS) LDA QQ11 ; If the current view is a chart (QQ11 = 64 or 128), AND #%11000000 ; keep going, otherwise return from the subroutine (as BEQ t95 ; t95 contains an RTS) JMP HME2 ; Jump to HME2 to let us search for a system, returning ; from the subroutine using a tail call .HME1 STA T1 ; Store A (the key that's been pressed) in T1 LDA QQ11 ; If the current view is a chart (QQ11 = 64 or 128), AND #%11000000 ; keep going, otherwise jump down to TT107 to skip the BEQ TT107 ; following LDA QQ22+1 ; If the on-screen hyperspace counter is non-zero, BNE TT107 ; then we are already counting down, so jump to TT107 ; to skip the following LDA T1 ; Restore the original value of A (the key that's been ; pressed) from T1 CMP #OINT ; If "O" was pressed, do the following three jumps, BNE ee2 ; otherwise skip to ee2 to continue JSR TT103 ; Draw small crosshairs at coordinates (QQ9, QQ10), ; which will erase the crosshairs currently there JSR ping ; Set the target system to the current system (which ; will move the location in (QQ9, QQ10) to the current ; home system JMP TT103 ; Draw small crosshairs at coordinates (QQ9, QQ10), ; which will draw the crosshairs at our current home ; system, and return from the subroutine using a tail ; call .ee2 JSR TT16 ; Call TT16 to move the crosshairs by the amount in X ; and Y, which were passed to this subroutine as ; arguments .TT107 LDA QQ22+1 ; If the on-screen hyperspace counter is zero, return BEQ t95 ; from the subroutine (as t95 contains an RTS), as we ; are not currently counting down to a hyperspace jump DEC QQ22 ; Decrement the internal hyperspace counter BNE t95 ; If the internal hyperspace counter is still non-zero, ; then we are still counting down, so return from the ; subroutine (as t95 contains an RTS) ; If we get here then the internal hyperspace counter ; has just reached zero and it wasn't zero before, so ; we need to reduce the on-screen counter and update ; the screen. We do this by first printing the next ; number in the countdown sequence, and then printing ; the old number, which will erase the old number ; and display the new one because printing uses EOR ; logic LDX QQ22+1 ; Set X = the on-screen hyperspace counter - 1 DEX ; (i.e. the next number in the sequence) JSR ee3 ; Print the 8-bit number in X at text location (0, 1) LDA #5 ; Reset the internal hyperspace counter to 5 STA QQ22 LDX QQ22+1 ; Set X = the on-screen hyperspace counter (i.e. the ; current number in the sequence, which is already ; shown on-screen) JSR ee3 ; Print the 8-bit number in X at text location (0, 1), ; i.e. print the hyperspace countdown in the top-left ; corner DEC QQ22+1 ; Decrement the on-screen hyperspace countdown BNE t95 ; If the countdown is not yet at zero, return from the ; subroutine (as t95 contains an RTS) JMP TT18 ; Otherwise the countdown has finished, so jump to TT18 ; to do a hyperspace jump, returning from the subroutine ; using a tail call .t95 RTS ; Return from the subroutine .T95 ; If we get here, "D" was pressed, so we need to show ; the distance to the selected system (if we are in a ; chart view) LDA QQ11 ; If the current view is a chart (QQ11 = 64 or 128), AND #%11000000 ; keep going, otherwise return from the subroutine (as BEQ t95 ; t95 contains an RTS) ;LDA #CYAN ; These instructions are commented out in the original ;JSR DOCOL ; source JSR hm ; Call hm to move the crosshairs to the target system ; in (QQ9, QQ10), returning with A = 0 STA QQ17 ; Set QQ17 = 0 to switch to ALL CAPS JSR cpl ; Print control code 3 (the selected system name) LDA #%10000000 ; Set bit 7 of QQ17 to switch to Sentence Case, with the STA QQ17 ; next letter in capitals LDA #12 ; Print a line feed to move the text cursor down a line JSR TT26 ;LDA #10 ; These instructions are commented out in the original ;JSR TT26 ; source ; ;LDA #1 ;JSR DOXC ; ;JSR INCYC JMP TT146 ; Print the distance to the selected system and return ; from the subroutine using a tail call ; ****************************************************************************** ; ; Name: BAD ; Type: Subroutine ; Category: Status ; Summary: Calculate how bad we have been ; ; ------------------------------------------------------------------------------ ; ; Work out how bad we are from the amount of contraband in our hold. The ; formula is: ; ; (slaves + narcotics) * 2 + firearms ; ; so slaves and narcotics are twice as illegal as firearms. The value in FIST ; (our legal status) is set to at least this value whenever we launch from a ; space station, and a FIST of 50 or more gives us fugitive status, so leaving a ; station carrying 25 tonnes of slaves/narcotics, or 50 tonnes of firearms ; across multiple trips, is enough to make us a fugitive. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A value that determines how bad we are from the amount ; of contraband in our hold ; ; ****************************************************************************** .BAD LDA QQ20+3 ; Set A to the number of tonnes of slaves in the hold CLC ; Clear the C flag so we can do addition without the ; C flag affecting the result ADC QQ20+6 ; Add the number of tonnes of narcotics in the hold ASL A ; Double the result and add the number of tonnes of ADC QQ20+10 ; firearms in the hold RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: FAROF ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Compare x_hi, y_hi and z_hi with 224 ; Deep dive: A sense of scale ; ; ------------------------------------------------------------------------------ ; ; Compare x_hi, y_hi and z_hi with 224, and set the C flag if all three <= 224, ; otherwise clear the C flag. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if x_hi <= 224 and y_hi <= 224 and z_hi <= 224 ; ; Clear otherwise (i.e. if any one of them are bigger than ; 224) ; ; ****************************************************************************** .FAROF LDA #224 ; Set A = 224 and fall through into FAROF2 to do the ; comparison ; ****************************************************************************** ; ; Name: FAROF2 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Compare x_hi, y_hi and z_hi with A ; ; ------------------------------------------------------------------------------ ; ; Compare x_hi, y_hi and z_hi with A, and set the C flag if all three <= A, ; otherwise clear the C flag. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if x_hi <= A and y_hi <= A and z_hi <= A ; ; Clear otherwise (i.e. if any one of them are bigger than ; A) ; ; ****************************************************************************** .FAROF2 CMP INWK+1 ; If A < x_hi, C will be clear so jump to FA1 to BCC FA1 ; return from the subroutine with C clear, otherwise ; C will be set so move on to the next one CMP INWK+4 ; If A < y_hi, C will be clear so jump to FA1 to BCC FA1 ; return from the subroutine with C clear, otherwise ; C will be set so move on to the next one CMP INWK+7 ; If A < z_hi, C will be clear, otherwise C will be set .FA1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MAS4 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Calculate a cap on the maximum distance to a ship ; ; ------------------------------------------------------------------------------ ; ; Logical OR the value in A with the high bytes of the ship's position (x_hi, ; y_hi and z_hi). ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A OR x_hi OR y_hi OR z_hi ; ; ****************************************************************************** .MAS4 ORA INWK+1 ; OR A with x_hi, y_hi and z_hi ORA INWK+4 ORA INWK+7 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: brkd ; Type: Variable ; Category: Utility routines ; Summary: A flag that indicates whether a system error has occurred ; ; ****************************************************************************** .brkd EQUB 0 ; A flag to record whether a system error has occurred, ; so we can print it out ; ; * 0 = no system error has occurred ; ; * $FF = a system error has occurred ; ****************************************************************************** ; ; Name: BRBR ; Type: Subroutine ; Category: Utility routines ; Summary: The standard BRKV handler for the game ; ; ------------------------------------------------------------------------------ ; ; This routine is unused in this version of Elite (it is left over from the ; 6502 Second Processor version). ; ; ****************************************************************************** .BRBR ; When we call this routine, we know that brkd will be ; zero, as it is initialised to zero and the only other ; place it gets changed is in the TITLE routine, where ; it also gets set to 0 DEC brkd ; Set brkd = $FF to indicate that there is a system ; error that needs to be printed out on the title screen ; by the TITLE routine LDX #$FF ; Set the stack pointer to $01FF, which is the standard TXS ; location for the 6502 stack, so this instruction ; effectively resets the stack JSR backtonormal ; Disable the keyboard and set the SVN flag to 0 TAY ; The call to backtonormal sets A to 0, so this sets Y ; to 0, which we use as a loop counter below LDA #7 ; Set A = 7 to generate a beep before we print the error ; message .BRBRLOOP JSR CHPR ; Print the character in A, which contains a line feed ; on the first loop iteration, and then any non-zero ; characters we fetch from the error message INY ; Increment the loop counter LDA ($FD),Y ; Fetch the Y-th byte of the block pointed to by ; ($FD $FE), so that's the Y-th character of the message ; pointed to by the error message pointer BNE BRBRLOOP ; If the fetched character is non-zero, loop back to the ; JSR OSWRCH above to print the it, and keep looping ; until we fetch a zero (which marks the end of the ; message) JMP BR1 ; Jump to BR1 to restart the game ; ****************************************************************************** ; ; Name: DEATH ; Type: Subroutine ; Category: Start and end ; Summary: Display the death screen ; ; ------------------------------------------------------------------------------ ; ; We have been killed, so display the chaos of our destruction above a "GAME ; OVER" sign, and clean up the mess ready for the next attempt. ; ; ****************************************************************************** .DEATH JSR EXNO3 ; Make the sound of us dying JSR RES2 ; Reset a number of flight variables and workspaces ASL DELTA ; Divide our speed in DELTA by 4 ASL DELTA LDX #24 ; Set the screen to only show 24 text rows, which hides JSR DET1 ; the dashboard, setting A to 6 in the process JSR TT66 ; Clear the top part of the screen, draw a border box, ; and set the current view type in QQ11 to 6 (death ; screen) JSR BOX ; Call BOX to redraw the same border box (BOX is part ; of TT66), which removes the border as it is drawn ; using EOR logic LDA #0 ; The BOX routine sets these addresses in the screen STA SCBASE+$1F1F ; bitmap to $FF and 1 respectively, but it doesn't use STA SCBASE+$118 ; EOR logic to do this, so we need to manually set them ; to 0 to remove the corresponding pixels from the ; screen, as the call we just made to BOX won't do this JSR nWq ; Create a cloud of stardust containing the correct ; number of dust particles (i.e. NOSTM of them) LDA #12 ; Move the text cursor to column 12 on row 12 JSR DOYC JSR DOXC ;LDA #YELLOW ; These instructions are commented out in the original ;JSR DOCOL ; source LDA #146 ; Print recursive token 146 ("{all caps}GAME OVER") JSR ex .D1 JSR Ze ; Call Ze to initialise INWK to a fairly aggressive ; ship, and set A and X to random values LSR A ; Set A = A / 4, so A is now between 0 and 63, and LSR A ; store in byte #0 (x_lo) STA INWK LDY #0 ; Set the following to 0: the current view in QQ11 STY QQ11 ; (space view), x_hi, y_hi, z_hi and the AI flag (no AI STY INWK+1 ; or E.C.M. and zero aggression) STY INWK+4 STY INWK+7 STY INWK+32 DEY ; Set Y = 255 STY MCNT ; Reset the main loop counter to 255, so all timer-based ; calls will be stopped EOR #%00101010 ; Flip bits 1, 3 and 5 in A (x_lo) to get another number STA INWK+3 ; between 48 and 63, and store in byte #3 (y_lo) ORA #%01010000 ; Set bits 4 and 6 of A to bump it up to between 112 and STA INWK+6 ; 127, and store in byte #6 (z_lo) TXA ; Set A to the random number in X and keep bits 0-3 and AND #%10001111 ; the sign in bit 7 to get a number between -15 and +15, STA INWK+29 ; and store in byte #29 (roll counter) to give our ship ; a gentle roll with damping LDY #64 ; Set the laser count to 64 to act as a counter in the STY LASCT ; D2 loop below, so this setting determines how long the ; death animation lasts (it's 64 * 2 iterations of the ; main flight loop) SEC ; Set the C flag ROR A ; This sets A to a number between 0 and +7, which we AND #%10000111 ; store in byte #30 (the pitch counter) to give our ship STA INWK+30 ; a very gentle downwards pitch with damping LDX #OIL ; Set X to #OIL, the ship type for a cargo canister LDA XX21-1+2*PLT ; Fetch the byte from location XX21 - 1 + 2 * PLT, which ; equates to XX21 + 7 (the high byte of the address of ; SHIP_PLATE), which seems a bit odd. It might make more ; sense to do LDA (XX21-2+2*PLT) as this would fetch the ; first byte of the alloy plate's blueprint (which ; determines what happens when alloys are destroyed), ; but there aren't any brackets, so instead this always ; returns $D0, which is never zero, so the following ; BEQ is never true. (If the brackets were there, then ; we could stop plates from spawning on death by setting ; byte #0 of the blueprint to 0... but then scooping ; plates wouldn't give us alloys, so who knows what this ; is all about?) BEQ D3 ; If A = 0, jump to D3 to skip the following instruction BCC D3 ; If the C flag is clear, which will be random following ; the above call to Ze, jump to D3 to skip the following ; instruction DEX ; Decrement X, which sets it to #PLT, the ship type for ; an alloy plate .D3 JSR fq1 ; Call fq1 with X set to #OIL or #PLT, which adds a new ; cargo canister or alloy plate to our local bubble of ; universe and points it away from us with double DELTA ; speed (i.e. 6, as DELTA was set to 3 by the call to ; RES2 above). INF is set to point to the new arrival's ; ship data block in K% JSR DORND ; Set A and X to random numbers and extract bit 7 from A AND #%10000000 LDY #31 ; Store this in byte #31 of the ship's data block, so it STA (INF),Y ; has a 50% chance of marking our new arrival as being ; killed (so it will explode) LDA FRIN+4 ; The call we made to RES2 before we entered the loop at BEQ D1 ; D1 will have reset all the ship slots at FRIN, so this ; checks to see if the fifth slot is empty, and if it ; is we loop back to D1 to add another canister, until ; we have added five of them JSR U% ; Clear the key logger, which also sets A = 0 STA DELTA ; Set our speed in DELTA to 0, as we aren't going ; anywhere any more JSR M% ; Call the M% routine to do the main flight loop once, ; which will display our exploding canister scene and ; move everything about, as well as decrementing the ; value in LASCT JSR NOSPRITES ; Call NOSPRITES to disable all sprites and remove them ; from the screen .D2 JSR M% ; Call the M% routine to do the main flight loop once, ; which will display our exploding canister scene and ; move everything about, as well as decrementing the ; value in LASCT DEC LASCT ; Decrement the counter in LASCT, which we set above, ; so for each loop around D2, we decrement LASCT by 5 ; (the main loop decrements it by 4, and this one makes ; it 5) BNE D2 ; Loop back to call the main flight loop again, until we ; have called it 127 times LDX #31 ; Set the screen to show all 31 text rows, which shows JSR DET1 ; the dashboard JMP DEATH2 ; Jump to DEATH2 to reset and restart the game ; ****************************************************************************** ; ; Name: spasto ; Type: Variable ; Category: Universe ; Summary: Contains the address of the Coriolis space station's ship ; blueprint ; ; ****************************************************************************** .spasto EQUW $8888 ; This variable is set by routine BEGIN to the address ; of the Coriolis space station's ship blueprint ; ****************************************************************************** ; ; Name: BEGIN ; Type: Subroutine ; Category: Loader ; Summary: Initialise the configuration variables and start the game ; ; ****************************************************************************** .BEGIN ;JSR BRKBK ; This instruction is commented out in the original ; source LDX #(MUSILLY-COMC) ; We start by zeroing all the configuration variables ; between COMC and MUSILLY, to set them to their default ; values, so set a counter in X for MUSILLY - COMC bytes LDA #0 ; Set A = 0 so we can zero the variables .BEL1 STA COMC,X ; Zero the X-th configuration variable DEX ; Decrement the loop counter BPL BEL1 ; Loop back to BEL1 to zero the next byte, until we have ; zeroed them all LDA XX21+SST*2-2 ; Set spasto(1 0) to the Coriolis space station entry STA spasto ; from the ship blueprint lookup table at XX21 (so LDA XX21+SST*2-1 ; spasto(1 0) points to the Coriolis blueprint) STA spasto+1 JSR JAMESON ; Call JAMESON to set the last saved commander to the ; default "JAMESON" commander ; Fall through into TT170 to start the game ; ****************************************************************************** ; ; Name: TT170 ; Type: Subroutine ; Category: Start and end ; Summary: Main entry point for the Elite game code ; Deep dive: Program flow of the main game loop ; ; ------------------------------------------------------------------------------ ; ; This is the main entry point for the main game code. ; ; ****************************************************************************** .TT170 LDX #$FF ; Set the stack pointer to $01FF, which is the standard TXS ; location for the 6502 stack, so this instruction ; effectively resets the stack JSR RESET ; Call RESET to initialise most of the game variables ; Fall through into DEATH2 to start the game ; ****************************************************************************** ; ; Name: DEATH2 ; Type: Subroutine ; Category: Start and end ; Summary: Reset most of the game and restart from the title screen ; ; ------------------------------------------------------------------------------ ; ; This routine is called following death, and when the game is quit by pressing ; ESCAPE when paused. ; ; ****************************************************************************** .DEATH2 LDX #$FF ; Set the stack pointer to $01FF, which is the standard TXS ; location for the 6502 stack, so this instruction ; effectively resets the stack JSR RES2 ; Reset a number of flight variables and workspaces ; and fall through into the entry code for the game ; to restart from the title screen ; ****************************************************************************** ; ; Name: BR1 (Part 1 of 2) ; Type: Subroutine ; Category: Start and end ; Summary: Show the "Load New Commander (Y/N)?" screen and start the game ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; QU5 Restart the game using the last saved commander without ; asking whether to load a new commander file ; ; ****************************************************************************** .BR1 JSR ZEKTRAN ; Call ZEKTRAN to clear the key logger LDA #3 ; Move the text cursor to column 3 JSR DOXC ;JSR FX200 ; This instruction is commented out in the original ; source IF _GMA_RELEASE JSR startat ; Start playing the title music ENDIF LDX #CYL ; Call TITLE to show a rotating Cobra Mk III (#CYL) and LDA #6 ; token 6 ("LOAD NEW {single cap}COMMANDER {all caps} LDY #210 ; (Y/N)?{sentence case}{cr}{cr}"), with the ship at a JSR TITLE ; distance of 210, returning with the internal number ; of the key pressed in A CMP #YINT ; Did we press "Y"? If not, jump to QU5, otherwise BNE QU5 ; continue on to load a new commander IF _GMA_RELEASE JSR stopat ; Stop playing the title music ENDIF JSR DFAULT ; Call DFAULT to reset the current commander data block ; to the last saved commander JSR SVE ; Call SVE to load a new commander into the last saved ; commander data block IF _GMA_RELEASE JSR startat ; Start playing the title music ENDIF .QU5 JSR DFAULT ; Call DFAULT to reset the current commander data block ; to the last saved commander ; ****************************************************************************** ; ; Name: BR1 (Part 2 of 2) ; Type: Subroutine ; Category: Start and end ; Summary: Show the "Press Fire or Space, Commander" screen and start the ; game ; ; ------------------------------------------------------------------------------ ; ; BRKV is set to point to BR1 by the loading process. ; ; ****************************************************************************** JSR msblob ; Reset the dashboard's missile indicators so none of ; them are targeted LDA #7 ; Call TITLE to show a rotating Adder (#ADA) and token LDX #ADA ; 7 ("PRESS SPACE OR FIRE,{single cap}COMMANDER.{cr} LDY #48 ; {cr}"), with the ship at a distance of 48, returning JSR TITLE ; with the internal number of the key pressed in A IF _GMA_RELEASE JSR stopat ; Stop playing the title music ENDIF JSR ping ; Set the target system coordinates (QQ9, QQ10) to the ; current system coordinates (QQ0, QQ1) we just loaded JSR TT111 ; Select the system closest to galactic coordinates ; (QQ9, QQ10) JSR jmp ; Set the current system to the selected system LDX #5 ; We now want to copy the seeds for the selected system ; in QQ15 into QQ2, where we store the seeds for the ; current system, so set up a counter in X for copying ; 6 bytes (for three 16-bit seeds) ; The label below is called likeTT112 because this code ; is almost identical to the TT112 loop in the hyp1 ; routine .likeTT112 LDA QQ15,X ; Copy the X-th byte in QQ15 to the X-th byte in QQ2 STA QQ2,X DEX ; Decrement the counter BPL likeTT112 ; Loop back to likeTT112 if we still have more bytes to ; copy INX ; Set X = 0 (as we ended the above loop with X = $FF) STX EV ; Set EV, the extra vessels spawning counter, to 0, as ; we are entering a new system with no extra vessels ; spawned LDA QQ3 ; Set the current system's economy in QQ28 to the STA QQ28 ; selected system's economy from QQ3 LDA QQ5 ; Set the current system's tech level in tek to the STA tek ; selected system's economy from QQ5 LDA QQ4 ; Set the current system's government in gov to the STA gov ; selected system's government from QQ4 ; Fall through into the docking bay routine below ; ****************************************************************************** ; ; Name: BAY ; Type: Subroutine ; Category: Status ; Summary: Go to the docking bay (i.e. show the Status Mode screen) ; ; ------------------------------------------------------------------------------ ; ; We end up here after the start-up process (load commander etc.), as well as ; after a successful save, an escape pod launch, a successful docking, the end ; of a cargo sell, and various errors (such as not having enough cash, entering ; too many items when buying, trying to fit an item to your ship when you ; already have it, running out of cargo space, and so on). ; ; ****************************************************************************** .BAY LDA #$FF ; Set QQ12 = $FF (the docked flag) to indicate that we STA QQ12 ; are docked LDA #f8 ; Jump into the main loop at FRCE, setting the key JMP FRCE ; that's "pressed" to the Status Mode key ; ****************************************************************************** ; ; Name: DFAULT ; Type: Subroutine ; Category: Start and end ; Summary: Reset the current commander data block to the last saved commander ; ; ****************************************************************************** .DFAULT LDX #NT%+8 ; The size of the last saved commander data block is NT% ; bytes, and it is preceded by the 8 bytes of the ; commander name (seven characters plus a carriage ; return). The commander data block at NAME is followed ; by the commander data block, so we need to copy the ; name and data from the "last saved" buffer at NA% to ; the current commander workspace at NAME. So we set up ; a counter in X for the NT% + 8 bytes that we want to ; copy .QUL1 LDA NA%-1,X ; Copy the X-th byte of NA%-1 to the X-th byte of STA NAME-1,X ; NAME-1 (the -1 is because X is counting down from ; NT% + 8 to 1) DEX ; Decrement the loop counter BNE QUL1 ; Loop back for the next byte of the commander data ; block STX QQ11 ; X is 0 by the end of the above loop, so this sets QQ11 ; to 0, which means we will be showing a view without a ; boxed title at the top (i.e. we're going to use the ; screen layout of a space view in the following) ; If the commander check below fails, we keep jumping ; back to here to crash the game with an infinite loop .doitagain JSR CHECK ; Call the CHECK subroutine to calculate the checksum ; for the current commander block at NA%+8 and put it ; in A CMP CHK ; Test the calculated checksum against CHK IF _REMOVE_CHECKSUMS NOP ; If we have disabled checksums, then ignore the result NOP ; of the comparison and fall through into the next part ELSE BNE doitagain ; If the calculated checksum does not match CHK, then ; loop back to repeat the check - in other words, we ; enter an infinite loop here, as the checksum routine ; will keep returning the same incorrect value ENDIF ; The checksum CHK is correct, so now we check whether ; CHK2 = CHK EOR A9, and if this check fails, bit 7 of ; the competition flags at COK gets set, to indicate ; to Acornsoft via the competition code that there has ; been some hacking going on with this competition entry EOR #$A9 ; X = checksum EOR $A9 TAX LDA COK ; Set A to the competition flags in COK CPX CHK2 ; If X = CHK2, then skip the next instruction BEQ tZ ORA #%10000000 ; Set bit 7 of A to indicate this commander file has ; been tampered with .tZ ORA #%01000000 ; Set bit 6 of A to denote that this is the Commodore 64 ; version STA COK ; Store the updated competition flags in COK JSR CHECK2 ; Call CHECK2 to calculate the third checksum for the ; last saved commander and return it in A CMP CHK3 ; Test the calculated checksum against CHK3 IF _REMOVE_CHECKSUMS NOP ; If we have disabled checksums, then ignore the result NOP ; of the comparison and fall through into the next part ELSE BNE doitagain ; If the calculated checksum does not match CHK3, then ; loop back to repeat the check - in other words, we ; enter an infinite loop here, as the checksum routine ; will keep returning the same incorrect value ENDIF RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TITLE ; Type: Subroutine ; Category: Start and end ; Summary: Display a title screen with a rotating ship and prompt ; ; ------------------------------------------------------------------------------ ; ; Display the title screen, with a rotating ship and a text token at the bottom ; of the screen. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The number of the extended token to show below the ; rotating ship (see variable TKN1 for details of ; recursive tokens) ; ; X The type of the ship to show (see variable XX21 for a ; list of ship types) ; ; Y The distance to show the ship rotating, once it has ; finished moving towards us ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; X If a key is being pressed, X contains the ASCII code ; of the key pressed ; ; ****************************************************************************** .TITLE STY distaway ; Store the ship distance in distaway PHA ; Store the token number on the stack for later STX TYPE ; Store the ship type in location TYPE IF _GMA_RELEASE LDA #$FF ; Set MULIE to $FF to prevent the RESET routine from STA MULIE ; stopping the title music that is currently playing ENDIF JSR RESET ; Reset our ship so we can use it for the rotating ; title ship IF _GMA_RELEASE LDA #0 ; Set MULIE back to 0 so the RESET routine goes back to STA MULIE ; its normal behaviour of stopping any music that is ; playing ENDIF JSR ZEKTRAN ; Call ZEKTRAN to clear the key logger LDA #32 ; Switch to the palette for the title view, though this JSR DOVDU19 ; doesn't actually do anything in this version of Elite LDA #13 ; Clear the top part of the screen, draw a border box, JSR TT66 ; and set the current view type in QQ11 to 13 (rotating ; ship view) ;LDA #RED ; These instructions are commented out in the original ;JSR DOCOL ; source LDA #0 ; Set QQ11 to 0, so from here on we are using a space STA QQ11 ; view LDA #96 ; Set nosev_z hi = 96 (96 is the value of unity in the STA INWK+14 ; rotation vector) LDA #96 ; Set A = 96 as the distance that the ship starts at STA INWK+7 ; Set z_hi, the high byte of the ship's z-coordinate, ; to 96, which is the distance at which the rotating ; ship starts out before coming towards us LDX #127 ; Set roll counter = 127, so don't dampen the roll and STX INWK+29 ; make the roll direction clockwise STX INWK+30 ; Set pitch counter = 127, so don't dampen the pitch and ; set the pitch direction to dive INX ; Set QQ17 to 128 (so bit 7 is set) to switch to STX QQ17 ; Sentence Case, with the next letter printing in upper ; case LDA TYPE ; Set up a new ship, using the ship type in TYPE JSR NWSHP LDA #6 ; Move the text cursor to column 6 JSR DOXC LDA #30 ; Print recursive token 144 ("---- E L I T E ----") JSR plf ; followed by a newline LDA #10 ; Print a line feed to move the text cursor down a line JSR TT26 LDA #6 ; Move the text cursor to column 6 again JSR DOXC LDA PATG ; If PATG = 0, skip the following two lines, which BEQ awe ; print the author credits (PATG can be toggled by ; pausing the game and pressing "X") LDA #13 ; Print extended token 13 ("BY D.BRABEN & I.BELL") JSR DETOK .awe LDA brkd ; If brkd = 0, jump to BRBR2 to skip the following, as BEQ BRBR2 ; we do not have a system error message to display ; If we get here then brkd = $FF, which indicates that ; we have a system error we need to display INC brkd ; Set brkd = 0 to clear the error flag and indicate that ; the error has been processed LDA #7 ; Move the text cursor to column 7 JSR DOXC LDA #10 ; Move the text cursor to row 10 JSR DOYC ; The following loop prints out the null-terminated ; message pointed to by ($FD $FE), which is the OS ; error message pointer - so this prints the error ; message on the next line LDY #0 ; Set Y = 0 to act as a character counter JSR CHPR ; Print the character in A (which contains a line feed ; on the first loop iteration), and then any non-zero ; characters we fetch from the error message INY ; Increment the loop counter LDA ($FD),Y ; Fetch the Y-th byte of the block pointed to by ; ($FD $FE), so that's the Y-th character of the message ; pointed to by the OS error message pointer BNE P%-6 ; If the fetched character is non-zero, loop back to the ; JSR CHPR above to print it, and keep looping until ; we fetch a zero (which marks the end of the message) .BRBR2 LDY #0 ; Set DELTA = 0 (i.e. ship speed = 0) STY DELTA STY JSTK ; Set JSTK = 0 (i.e. keyboard, not joystick) LDA #15 ; Move the text cursor to row 15 STA YC LDA #1 ; Move the text cursor to column 1 STA XC PLA ; Restore the recursive token number we stored on the ; stack at the start of this subroutine ;JSR ex ; This instruction is commented out in the original ; source (it would print the recursive token in A) JSR DETOK ; Print the extended token in A LDA #3 ; Move the text cursor to column 3 JSR DOXC LDA #12 ; Print extended token 12 ("{single cap}C) {single JSR DETOK ; cap}D.{single cap}BRABEN & {single cap}I.{single ; cap}BELL 1985") LDA #12 ; Set CNT2 = 12 as the outer loop counter for the loop STA CNT2 ; starting at TLL2 LDA #5 ; Set the main loop counter in MCNT to 5, to act as the STA MCNT ; inner loop counter for the loop starting at TLL2 LDA #$FF ; Set JSTK = $FF (i.e. joystick, not keyboard), which STA JSTK ; we will change later if joysticks are not chosen .TLL2 LDA INWK+7 ; If z_hi (the ship's distance) is 1, jump to TL1 to CMP #1 ; skip the following decrement BEQ TL1 DEC INWK+7 ; Decrement the ship's distance, to bring the ship ; a bit closer to us .TL1 JSR MVEIT ; Move the ship in space according to the orientation ; vectors and the new value in z_hi LDX distaway ; Set z_lo to the distance value we passed to the STX INWK+6 ; routine, so this is the closest the ship gets to us LDA MCNT ; This has no effect - it is presumably left over from AND #3 ; the other versions of Elite which only scan the ; keyboard once every four loops, but that isn't the ; case here as the result is not acted upon LDA #0 ; Set x_lo = 0, so the ship remains in the screen centre STA INWK STA INWK+3 ; Set y_lo = 0, so the ship remains in the screen centre JSR LL9 ; Call LL9 to display the ship JSR RDKEY ; Scan the keyboard for a key press and return the ; internal key number in A and X (or 0 for no key press) ; ; This also clears the C flag if no keys are being ; pressed DEC MCNT ; Decrement the main loop counter BIT KY7 ; If the joystick's fire button is being pressed, jump BMI TL3 ; to TL3 to leave joysticks configured (i.e. JSTK = $FF) BCC TLL2 ; If no key is being pressed then the C flag will be ; clear from the call to RDKEY, so loop back up to ; move/rotate the ship and check again for a key press INC JSTK ; The joystick fire button was not pressed, so set JSTK ; to 0 (it was set to $FF above), to enable keyboard and ; disable joysticks .TL3 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: CHECK ; Type: Subroutine ; Category: Save and load ; Summary: Calculate the checksum for the last saved commander data block ; Deep dive: Commander save files ; ; ------------------------------------------------------------------------------ ; ; The checksum for the last saved commander data block is saved as part of the ; commander file, in two places (CHK AND CHK2), to protect against file ; tampering. This routine calculates the checksum and returns it in A. ; ; This algorithm is also implemented in elite-checksum.py. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A The checksum for the last saved commander data block ; ; ****************************************************************************** .CHECK LDX #NT%-3 ; Set X to the size of the commander data block, less ; 3 (as there are two checksum bytes and the save count) CLC ; Clear the C flag so we can do addition without the ; C flag affecting the result TXA ; Seed the checksum calculation by setting A to the ; size of the commander data block, less 2 ; We now loop through the commander data block, ; starting at the end and looping down to the start ; (so at the start of this loop, the X-th byte is the ; last byte of the commander data block, i.e. the save ; count) .QUL2 ADC NA%+7,X ; Add the X-1-th byte of the data block to A, plus the ; C flag EOR NA%+8,X ; EOR A with the X-th byte of the data block DEX ; Decrement the loop counter BNE QUL2 ; Loop back for the next byte in the calculation, until ; we have added byte #0 and EOR'd with byte #1 of the ; data block RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: CHECK2 ; Type: Subroutine ; Category: Save and load ; Summary: Calculate the third checksum for the last saved commander data ; block (Commodore 64 and Apple II versions only) ; ; ****************************************************************************** .CHECK2 LDX #NT%-3 ; Set X to the size of the commander data block, less ; 3 (as there are two checksum bytes and the save count) CLC ; Clear the C flag so we can do addition without the ; C flag affecting the result TXA ; Seed the checksum calculation by setting A to the ; size of the commander data block, less 2 ; We now loop through the commander data block, ; starting at the end and looping down to the start ; (so at the start of this loop, the X-th byte is the ; last byte of the commander data block, i.e. the save ; count) .QU2L2 STX T ; Set A = A EOR X EOR T ; ROR A ; This additional step is the only difference between ; the original checksum from BBC Micro Elite (in CHECK), ; and this additional checksum in the Commodore 64 and ; Apple II versions ADC NA%+7,X ; Add the X-1-th byte of the data block to A, plus the ; C flag EOR NA%+8,X ; EOR A with the X-th byte of the data block DEX ; Decrement the loop counter BNE QU2L2 ; Loop back for the next byte in the calculation, until ; we have added byte #0 and EOR'd with byte #1 of the ; data block RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: JAMESON ; Type: Subroutine ; Category: Save and load ; Summary: Restore the default JAMESON commander ; ; ****************************************************************************** .JAMESON LDY #(NAEND%-NA2%) ; We are going to copy the default commander at NA2% ; over the top of the last saved commander at NA%, so ; set a counter to copy all the bytes between NA2% and ; NAEND% .JAMEL1 LDA NA2%,Y ; Copy the Y-th byte of NA2% to the Y-th byte of NA% STA NA%,Y DEY ; Decrement the loop counter BPL JAMEL1 ; Loop back until we have copied the whole commander LDY #7 ; Set oldlong to 7, the length of the commander name STY oldlong ; "JAMESON" RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TRNME ; Type: Subroutine ; Category: Save and load ; Summary: Copy the last saved commander's name from INWK to NA% ; ; ****************************************************************************** .TRNME LDX #7 ; The commander's name can contain a maximum of 7 ; characters, and is terminated by a carriage return, ; so set up a counter in X to copy 8 characters LDA thislong ; Copy the length of the commander's name from thislong STA oldlong ; to oldlong (though this is never used, so this ; doesn't have any effect) .GTL1 LDA INWK+5,X ; Copy the X-th byte of INWK+5 to the X-th byte of NA% STA NA%,X DEX ; Decrement the loop counter BPL GTL1 ; Loop back until we have copied all 8 bytes ; Fall through into TR1 to copy the name back from NA% ; to INWK. This isn't necessary as the name is already ; there, but it does save one byte, as we don't need an ; RTS here ; ****************************************************************************** ; ; Name: TR1 ; Type: Subroutine ; Category: Save and load ; Summary: Copy the last saved commander's name from NA% to INWK ; ; ****************************************************************************** .TR1 LDX #7 ; The commander's name can contain a maximum of 7 ; characters, and is terminated by a carriage return, ; so set up a counter in X to copy 8 characters .GTL2 LDA NA%,X ; Copy the X-th byte of NA% to the X-th byte of INWK+5 STA INWK+5,X DEX ; Decrement the loop counter BPL GTL2 ; Loop back until we have copied all 8 bytes RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: GTNMEW ; Type: Subroutine ; Category: Save and load ; Summary: Fetch the name of a commander file to save or load ; ; ------------------------------------------------------------------------------ ; ; Get the commander's name for loading or saving a commander file. The name is ; stored in the INWK workspace and is terminated by a return character (13). ; ; If ESCAPE is pressed or a blank name is entered, then the name stored is set ; to the name from the last saved commander block. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; INWK The full filename, including drive and directory, in ; the form ":0.E.JAMESON", for example, terminated by a ; return character (13) ; ; ****************************************************************************** .GTNMEW ;LDY #8 ; These instructions are commented out in the original ;JSR DELAY ; source .GTNME LDX #4 ; First we want to copy the drive and directory part of ; the commander file from NA%-5, so set a counter in X ; for 5 bytes, as the string is of the form ":0.E." .GTL3 LDA NA%-5,X ; Copy the X-th byte from NA%-5 to INWK STA INWK,X DEX ; Decrement the loop counter BPL GTL3 ; Loop back until the whole drive and directory string ; has been copied to INWK to INWK+4 LDA #7 ; The call to MT26 below uses the OSWORD block at RLINE STA RLINE+2 ; to fetch the line, and RLINE+2 defines the maximum ; line length allowed, so this changes the maximum ; length to 7 (as that's the longest commander name ; allowed) LDA #8 ; Print extended token 8 ("{single cap}COMMANDER'S JSR DETOK ; NAME? ") JSR MT26 ; Call MT26 to fetch a line of text from the keyboard ; to INWK+5, with the text length in Y, so INWK now ; contains the full pathname of the file, as in ; ":0.E.JAMESON", for example LDA #9 ; Reset the maximum length in RLINE+2 to the original STA RLINE+2 ; value of 9 TYA ; The OSWORD call returns the length of the commander's ; name in Y, so transfer this to A BEQ TR1 ; If A = 0, no name was entered, so jump to TR1 to copy ; the last saved commander's name from NA% to INWK ; and return from the subroutine there STY thislong ; Store the length of the length of the commander's that ; was entered in thislong RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MT26 ; Type: Subroutine ; Category: Text ; Summary: Fetch a line of text from the keyboard ; Deep dive: Extended text tokens ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Y The size of the entered text, or 0 if none was entered ; ; INWK+5 The entered text, terminated by a carriage return ; ; C flag Set if ESCAPE was pressed ; ; ****************************************************************************** .MT26 LDA #MAG2 ; Switch the text colour to purple STA COL2 LDY #8 ; Wait for 8/50 of a second (0.16 seconds) on PAL JSR DELAY ; systems, or 8/60 of a second (0.13 seconds) on NTSC JSR FLKB ; Call FLKB to flush the keyboard buffer LDY #0 ; Set Y = 0 to hold the length of the text entered .OSW0L JSR TT217 ; Scan the keyboard until a key is pressed, and return ; the key's ASCII code in A (and X) CMP #13 ; If RETURN was pressed, jump to OSW03 BEQ OSW03 CMP #27 ; If ESCAPE was pressed, jump to OSW04 BEQ OSW04 CMP #127 ; If DELETE was pressed, jump to OSW05 BEQ OSW05 CPY RLINE+2 ; If Y >= RLINE+2 (the maximum line length from the BCS OSW01 ; OSWORD configuration block at RLINE), then jump to ; OSW01 to give an error beep as we have reached the ; character limit CMP RLINE+3 ; If the key pressed is less than the character in BCC OSW01 ; RLINE+3 (the lowest allowed character from the OSWORD ; configuration block at RLINE), then jump to OSW01 ; to give an error beep as the key pressed is out of ; range CMP RLINE+4 ; If the key pressed is greater than or equal to the BCS OSW01 ; character in RLINE+4 (the highest allowed character ; from the OSWORD configuration block at RLINE), then ; jump to OSW01 to give an error beep as the key ; pressed is out of range STA INWK+5,Y ; Store the key's ASCII code in the Y-th byte of INWK+5 INY ; Increment Y to point to the next free byte in INWK+5 EQUB $2C ; Skip the next instruction by turning it into ; $2C $A9 $07, or BIT $07A9, which does nothing apart ; from affect the flags .OSW01 LDA #7 ; Set A to the beep character, so the next instruction ; makes a system beep .OSW06 JSR CHPR ; Print the character in A (and clear the C flag) BCC OSW0L ; Loop back to OSW0L to fetch another key press (this ; BCC is effectively a JMP as CHPR clears the C flag) .OSW03 STA INWK+5,Y ; Store the return character in the Y-th byte of INWK+5 LDA #$10 ; Switch the text colour to white STA COL2 LDA #12 ; Print a newline and return from the subroutine using a JMP CHPR ; tail call .OSW04 LDA #$10 ; Switch the text colour to white STA COL2 SEC ; Set the C flag as ESCAPE was pressed RTS ; Return from the subroutine .OSW05 TYA ; If the length of the line so far in Y is 0, then we BEQ OSW01 ; just pressed DELETE on an empty line, so jump to ; OSW01 give an error beep DEY ; Otherwise we want to delete a character, so decrement ; the length of the line so far in Y LDA #127 ; Set A = 127 and jump back to OSW06 to print the BNE OSW06 ; character in A (i.e. the DELETE character) and listen ; for the next key press ; ****************************************************************************** ; ; Name: RLINE ; Type: Variable ; Category: Text ; Summary: The OSWORD configuration block used to fetch a line of text from ; the keyboard ; ; ------------------------------------------------------------------------------ ; ; This block is left over from the BBC Micro version of Elite and is not used in ; this version. ; ; ****************************************************************************** .RLINE EQUW INWK+5 ; The address to store the input, so the text entered ; will be stored in INWK+5 as it is typed EQUB 9 ; Maximum line length = 9, as that's the maximum size ; for a commander's name including a directory name EQUB '!' ; Allow ASCII characters from "!" through to "{" in EQUB '{' ; the input ; ****************************************************************************** ; ; Name: FILEPR ; Type: Subroutine ; Category: Save and load ; Summary: Display the currently selected media (disk or tape) ; Deep dive: Extended text tokens ; ; ****************************************************************************** .FILEPR LDA #3 ; Print extended token 3 + DISK, i.e. token 3 or 2 (as CLC ; DISK can be 0 or $FF). Token 2 is "disk" and token 3 ADC DISK ; is "tape", so this displays the currently selected JMP DETOK ; media ; ****************************************************************************** ; ; Name: OTHERFILEPR ; Type: Subroutine ; Category: Save and load ; Summary: Display the non-selected media (disk or tape) ; Deep dive: Extended text tokens ; ; ****************************************************************************** .OTHERFILEPR LDA #2 ; Print extended token 2 - DISK, i.e. token 2 or 3 (as SEC ; DISK can be 0 or $FF). Token 2 is "disk" and token 3 SBC DISK ; is "tape", so this displays the other, non-selected JMP DETOK ; media ; ****************************************************************************** ; ; Name: ZERO ; Type: Subroutine ; Category: Utility routines ; Summary: Reset the local bubble of universe and ship status ; ; ------------------------------------------------------------------------------ ; ; This resets the following workspaces to zero: ; ; * UP workspace variables from FRIN to de, which include the ship slots for ; the local bubble of universe, and various flight and ship status variables ; ; ****************************************************************************** .ZERO LDX #(de-FRIN) ; We're going to zero the UP workspace variables from ; FRIN to de, so set a counter in X for the correct ; number of bytes LDA #0 ; Set A = 0 so we can zero the variables .ZEL2 STA FRIN,X ; Zero the X-th byte of FRIN to de DEX ; Decrement the loop counter BPL ZEL2 ; Loop back to zero the next variable until we have done ; them all RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: ZEBC ; Type: Subroutine ; Category: Utility routines ; Summary: Zero-fill pages $B and $C ; ; ****************************************************************************** .ZEBC RTS ; Return from the subroutine, as ZEBC does nothing in ; this version of Elite (it is left over from the BBC ; Micro version) LDX #$C ; Call ZES1 with X = $C to zero-fill page $C JSR ZES1 DEX ; Decrement X to $B ; Fall through into ZES1 to zero-fill page $B ; ****************************************************************************** ; ; Name: ZES1 ; Type: Subroutine ; Category: Utility routines ; Summary: Zero-fill the page whose number is in X ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The page we want to zero-fill ; ; ****************************************************************************** .ZES1 LDY #0 ; If we set Y = SC = 0 and fall through into ZES2 STY SC ; below, then we will zero-fill 255 bytes starting from ; SC - in other words, we will zero-fill the whole of ; page X ; ****************************************************************************** ; ; Name: ZES2 ; Type: Subroutine ; Category: Utility routines ; Summary: Zero-fill a specific page ; ; ------------------------------------------------------------------------------ ; ; Zero-fill from address (X SC) + Y to (X SC) + $FF. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The high byte (i.e. the page) of the starting point of ; the zero-fill ; ; Y The offset from (X SC) where we start zeroing, counting ; up to $FF ; ; SC The low byte (i.e. the offset into the page) of the ; starting point of the zero-fill ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Z flag Z flag is set ; ; ****************************************************************************** .ZES2 LDA #0 ; Load A with the byte we want to fill the memory block ; with - i.e. zero STX SC+1 ; We want to zero-fill page X, so store this in the ; high byte of SC, so the 16-bit address in SC and ; SC+1 is now pointing to the SC-th byte of page X .ZEL1 STA (SC),Y ; Zero the Y-th byte of the block pointed to by SC, ; so that's effectively the Y-th byte before SC INY ; Increment the loop counter BNE ZEL1 ; Loop back to zero the next byte RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: SVE ; Type: Subroutine ; Category: Save and load ; Summary: Display the disk access menu and process saving of commander files ; Deep dive: Commander save files ; The competition code ; ; ****************************************************************************** .SVE LDA #1 ; Print extended token 1, the disk access menu, which JSR DETOK ; presents these options: ; ; 1. Load New Commander ; 2. Save Commander {commander name} ; 3. Change to {other media} ; 4. Default JAMESON ; 5. Exit JSR t ; Scan the keyboard until a key is pressed, returning ; the ASCII code in A and X CMP #'1' ; Option 1 was chosen, so jump to loading to load a new BEQ loading ; commander CMP #'2' ; Option 2 was chosen, so jump to SV1 to save the BEQ SV1 ; current commander CMP #'3' ; Option 3 was chosen, so jump to feb10 to change to the BEQ feb10 ; other media CMP #'4' ; If option 4 wasn't chosen, jump to feb13 to exit the BNE feb13 ; menu LDA #224 ; Option 4 was chosen, so print extended token 224 JSR DETOK ; ("ARE YOU SURE?") JSR YESNO ; Call YESNO to wait until either "Y" or "N" is pressed BCC feb13 ; If "N" was pressed, jump to feb13 JSR JAMESON ; Otherwise "Y" was pressed, so call JAMESON to set the ; last saved commander to the default "JAMESON" ; commander JMP DFAULT ; Jump to DFAULT to reset the current commander data ; block to the last saved commander, returning from the ; subroutine using a tail call .feb13 CLC ; Option 5 was chosen, so clear the C flag to indicate ; that nothing was loaded RTS ; Return from the subroutine .feb10 LDA DISK ; Toggle the value of DISK between 0 and $FF to swap the EOR #$FF ; current media between tape and disk STA DISK JMP SVE ; Jump to SVE to display the disk access menu and return ; from the subroutine using a tail call .loading JSR GTNMEW ; If we get here then option 1 (load) was chosen, so ; call GTNMEW to fetch the name of the commander file ; to load (including drive number and directory) into ; INWK JSR LOD ; Call LOD to load the commander file JSR TRNME ; Transfer the commander filename from INWK to NA% SEC ; Set the C flag to indicate we loaded a new commander .jan2186 RTS ; Return from the subroutine .SV1 JSR GTNMEW ; If we get here then option 2 (save) was chosen, so ; call GTNMEW to fetch the name of the commander file ; to save (including drive number and directory) into ; INWK JSR TRNME ; Transfer the commander filename from INWK to NA% LSR SVC ; Halve the save count value in SVC LDA #4 ; Print extended token 4 ("COMPETITION NUMBER:") JSR DETOK LDX #NT% ; We now want to copy the current commander data block ; from location TP to the last saved commander block at ; NA%+8, so set a counter in X to copy the NT% bytes in ; the commander data block .SVL1 LDA TP,X ; Copy the X-th byte of TP to the X-th byte of NA%+8 ;STA $0B00,X ; STA NA%+8,X ; The STA is commented out in the original source DEX ; Decrement the loop counter BPL SVL1 ; Loop back until we have copied all the bytes in the ; commander data block JSR CHECK2 ; Call CHECK2 to calculate the third checksum for the ; last saved commander and return it in A STA CHK3 ; Store the checksum in CHK3, which is at the end of the ; last saved commander block JSR CHECK ; Call CHECK to calculate the checksum for the last ; saved commander and return it in A STA CHK ; Store the checksum in CHK, which is at the end of the ; last saved commander block PHA ; Store the checksum on the stack ORA #%10000000 ; Set K = checksum with bit 7 set STA K EOR COK ; Set K+2 = K EOR COK (the competition flags) STA K+2 EOR CASH+2 ; Set K+1 = K+2 EOR CASH+2 (the third cash byte) STA K+1 EOR #$5A ; Set K+3 = K+1 EOR $5A EOR TALLY+1 (the high byte of EOR TALLY+1 ; the kill tally) STA K+3 CLC ; Clear the C flag so the call to BPRNT does not include ; a decimal point JSR BPRNT ; Print the competition number stored in K to K+3. The ; value of U might affect how this is printed, and as ; it's a temporary variable in zero page that isn't ; reset by ZERO, it might have any value, but as the ; competition code is a 10-digit number, this just means ; it may or may not have an extra space of padding JSR TT67 ; Call TT67 twice to print two newlines JSR TT67 PLA ; Restore the checksum from the stack ;STA $0B00+NT% ; This instruction is commented out in the original ; source EOR #$A9 ; Store the checksum EOR $A9 in CHK2, the penultimate STA CHK2 ; byte of the last saved commander block ;STA $AFF+NT% ; This instruction is commented out in the original ; source ;LDA #0 ; These instructions are commented out in the original ;JSR QUS1 ; source JSR KERNALSETUP ; Set up memory so we can use the Kernal functions, ; which includes swapping the contents of zero page with ; the page at $CE00 (so the Kernal functions get a zero ; page that works for them, and any changes they make do ; not corrupt the game's zero page variables) ; ; This also enables interrupts that are generated by ; timer A underflow LDA #LO(NA%+8) ; Set $FD(1 0) = NA%+8 STA $FD ; LDA #HI(NA%+8) ; This sets the address at zero page location $FD and STA $FE ; $FD+1 to NA%+8, which is the address of the commander ; data that we want to save LDA #$FD ; Call the Kernal's SAVE function to save the commander LDX #LO(CHK+1) ; file as follows: LDY #HI(CHK+1) ; JSR KERNALSVE ; * A = address in zero page of the start address of ; the memory block to save, so this makes SAVE ; save the data from NA%+8 onwards ; ; * (Y X) = address of the end of the block of memory ; to save + 1, so this makes SAVE save the ; data from NA%+8 to CHK (inclusive) PHP ; If something goes wrong with the save then the C flag ; will be set, so save this on the stack so we can check ; it below SEI ; Disable interrupts while we configure the CIA and ; VIC-II BIT CIA+$D ; Reading from register $D of CIA1 will acknowledge any ; interrupts and clear them, so this line acknowledges ; any pending interrupts that might be waiting to be ; processed (using a BIT reads the location without ; changing any CPU registers - it only affects the ; flags, which we can simply ignore) LDA #%00000001 ; Set CIA1 register $0D to enable and disable interrupts STA CIA+$D ; as follows: ; ; * Bit 0 set = configure interrupts generated by ; timer A underflow ; ; * Bits 1-4 clear = do not change configuration of ; other interrupts ; ; * Bit 7 clear = disable interrupts whose ; corresponding bits are set ; ; So this disables interrupts that are generated by ; timer A underflow, while leaving other interrupts as ; they are LDX #0 ; Set the raster count to 0 to initialise the raster STX RASTCT ; effects in the COMIRQ handler (such as the split ; screen) INX ; Set bit 0 of VIC register $1A and clear bits 1-3 to STX VIC+$1A ; configure the following interrupts: ; ; * Bit 0 = enable raster interrupt ; ; * Bit 1 = disable sprite-background collision ; interrupt ; ; * Bit 2 = disable sprite-sprite collision interrupt ; ; * Bit 3 = disable light pen interrupt LDA VIC+$11 ; Clear bit 7 of VIC register $11, to clear the top bit AND #%01111111 ; of the raster line that generates the interrupt (as STA VIC+$11 ; the line number is a 9-bit value, with bits 0-7 in VIC ; register $12) LDA #40 ; Set VIC register $11 to 40, so along with bit 7 of VIC STA VIC+$12 ; register $10, this sets the raster interrupt to be ; generated when the raster reaches line 40 LDA #%100 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore CLI ; Enable interrupts again JSR SWAPPZERO ; The call to KERNALSETUP above swapped the contents of ; zero page with the page at $CE00, to ensure the Kernal ; routines ran with their copy of zero page rather than ; the game's zero page ; ; We are done using the Kernal functions, so now we swap ; them back so the Kernal's zero page is moved to $CE00 ; again, ready for next time, and the game's zero page ; variables are once again set up, ready for the game ; code to use PLP ; Retrieve the processor flags that we stashed after the ; call to KERNALSVE above CLI ; Enable interrupts to make sure the PHP doesn't disable ; interrupts (which it could feasibly do by restoring a ; set I flag) BCS saveerror ; If KERNALSVE returns with the C flag set then this ; indicates that a save error occurred, so jump to ; tapeerror via saveerror to print either "TAPE ERROR" ; or "DISK ERROR" JSR DFAULT ; Call DFAULT to reset the current commander data block ; to the last saved commander JSR t ; Scan the keyboard until a key is pressed, returning ; the ASCII code in A and X .SVEX CLC ; Clear the C flag to indicate we didn't just load a new ; commander file RTS ; Return from the subroutine .saveerror JMP tapeerror ; Jump to tapeerror to print either "TAPE ERROR" or ; "DISK ERROR" (this JMP enables us to use a branch ; instruction to jump to tapeerror) ; ****************************************************************************** ; ; Name: thislong ; Type: Variable ; Category: Save and load ; Summary: Contains the length of the most recently entered commander name ; ; ****************************************************************************** .thislong EQUB 7 ; ****************************************************************************** ; ; Name: oldlong ; Type: Variable ; Category: Save and load ; Summary: Contains the length of the last saved commander name ; ; ****************************************************************************** .oldlong EQUB 7 ; ****************************************************************************** ; ; Name: KERNALSETUP ; Type: Subroutine ; Category: Save and load ; Summary: Set up memory and interrupts so we can use the Kernal functions ; and configure the file system device number and filename ; ; ****************************************************************************** .KERNALSETUP JSR SWAPPZERO ; Swap the contents of zero page with the page at $CE00, ; which we filled with the contents of zero page when we ; started the game ; ; This ensures that the Kernal functions get a zero page ; that works for them, and we can repeat the swap once ; we are done with the Kernal functions to ensure any ; changes they make do not corrupt the game's zero page ; variables LDA #%110 ; Set A to pass to the call to SETL1 so we page the ; Kernal ROM and I/O into the memory map SEI ; Disable interrupts so we can scan the keyboard ; without being hijacked JSR SETL1 ; Call SETL1 to set the 6510 input/output port to the ; following: ; ; * LORAM = 0 ; * HIRAM = 1 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on, and ; $E000-$FFFF, which gets mapped to the Kernal ROM ; ; See the memory map at the bottom of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore LDA #0 ; Clear bits 0-3 in VIC register $1A to disable the STA VIC+$1A ; following interrupts: ; ; * Bit 0 = raster interrupt ; ; * Bit 1 = sprite-background collision interrupt ; ; * Bit 2 = sprite-sprite collision interrupt ; ; * Bit 3 = light pen interrupt CLI ; Allow interrupts again (or, as a comment in the ; original source says, "tell Ian to go away") LDA #%10000001 ; Set CIA1 register $0D to enable and disable interrupts STA CIA+$D ; as follows: ; ; * Bit 0 set = configure interrupts generated by ; timer A underflow ; ; * Bits 1-4 clear = do not change configuration of ; other interrupts ; ; * Bit 7 set = enable interrupts whose corresponding ; bits are set ; ; So this enables interrupts that are generated by timer ; A underflow, while leaving other interrupts as they ; are LDA #%11000000 ; Call the Kernal's SETMSG function to set the system JSR KERNALSETMSG ; error display switch as follows: ; ; * Bit 6 set = display I/O error messages ; ; * Bit 7 set = display system messages ; ; This ensures that any file system errors are shown LDX DISK ; Set X = DISK + 1 INX ; ; DISK is $FF (i.e. -1) for disk and 0 for tape, so this ; sets X to 0 for disk and 1 for tape LDA filesys,X ; Set X to the device number for the current media from TAX ; the lookup tape at filesys, so X is now 1 for tape or ; 8 for disk LDA #1 ; Call the Kernal's SETLFS function to set the file LDY #0 ; parameters as follows: JSR KERNALSETLFS ; ; * A = logical number 1 ; ; * X = device number 1 (tape) or 8 (disk) ; ; * Y = secondary address 0 ; ; The last setting enables us to specify a load address ; in (Y X) when using the Kernal's LOAD function to load ; a commander file in the LOD routine ; Before calling KERNALSETUP, the filename we want to ; work with has already been put into INWK+5 by the MT26 ; routine, with the length of the filename in thislong ; ; The address of the filename is INWK+5 because the ; first five characters of INWK contain a BBC Micro ; pathname like ":0.E.", which we can ignore in the ; Commodore 64 version LDA thislong ; Call SETNAM to set the filename parameters as LDX #(INWK+5) ; follows: LDY #0 ; JMP KERNALSETNAM ; * A = filename length ; ; * (Y X) = address of filename (Y is set to zero as ; INWK is in zero page) ; ; The call to SETNAM returns from the subroutine using ; a tail call ; ****************************************************************************** ; ; Name: GTDRV ; Type: Subroutine ; Category: Save and load ; Summary: Get an ASCII disk drive number from the keyboard ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A The ASCII value of the entered drive number ("0" to "3") ; ; C flag Clear if a valid drive number was entered (0-3), set ; otherwise ; ; ****************************************************************************** .GTDRV LDA #2 ; Print extended token 2 ("{cr}WHICH DRIVE?") JSR DETOK JSR t ; Scan the keyboard until a key is pressed, returning ; the ASCII code in A and X ORA #%00010000 ; Set bit 4 of A, perhaps to avoid printing any control ; characters in the next instruction JSR CHPR ; Print the character in A PHA ; Store A on the stack so we can retrieve it after the ; call to FEED JSR FEED ; Print a newline PLA ; Restore A from the stack CMP #'0' ; If A < ASCII "0", then it is not a valid drive number, BCC LOR ; so jump to LOR to set the C flag and return from the ; subroutine CMP #'4' ; If A >= ASCII "4", then it is not a valid drive ; number, and this CMP sets the C flag, otherwise it is ; a valid drive number in the range 0-3, so clear it RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: filesys ; Type: Variable ; Category: Save and load ; Summary: A lookup table containing the device numbers for tape and disk ; ; ****************************************************************************** .filesys EQUB 8 ; The device number for disk EQUB 1 ; The device number for tape ; ****************************************************************************** ; ; Name: LOD ; Type: Subroutine ; Category: Save and load ; Summary: Load a commander file ; ; ------------------------------------------------------------------------------ ; ; The filename should be stored at INWK, terminated with a carriage return (13). ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; LOR Set the C flag and return from the subroutine ; ; ****************************************************************************** .LOD JSR KERNALSETUP ; Set up memory so we can use the Kernal functions, ; which includes swapping the contents of zero page with ; the page at $CE00 (so the Kernal functions get a zero ; page that works for them, and any changes they make do ; not corrupt the game's zero page variables) LDA #0 ; Call the Kernal's LOAD function to load the commander LDX #LO(TAP%) ; file as follows: LDY #HI(TAP%) ; JSR KERNALLOAD ; * A = 0 to initiate a load operation ; ; * (Y X) = load address, so we load the commander to ; address TAP% PHP ; If something goes wrong with the save then the C flag ; will be set, so save this on the stack so we can check ; it below LDA #%00000001 ; Set CIA1 register $0D to enable and disable interrupts STA CIA+$D ; as follows: ; ; * Bit 0 set = configure interrupts generated by ; timer A underflow ; ; * Bits 1-4 clear = do not change configuration of ; other interrupts ; ; * Bit 7 clear = disable interrupts whose ; corresponding bits are set ; ; So this disables interrupts that are generated by ; timer A underflow, while leaving other interrupts as ; they are SEI ; Disable interrupts while we configure the VIC-II LDX #0 ; Set the raster count to 0 to initialise the raster STX RASTCT ; effects in the COMIRQ handler (such as the split ; screen) INX ; Set bit 0 of VIC register $1A and clear bits 1-3 to STX VIC+$1A ; configure the following interrupts: ; ; * Bit 0 = enable raster interrupt ; ; * Bit 1 = disable sprite-background collision ; interrupt ; ; * Bit 2 = disable sprite-sprite collision interrupt ; ; * Bit 3 = disable light pen interrupt LDA VIC+$11 ; Clear bit 7 of VIC register $11, to clear the top bit AND #%01111111 ; of the raster line that generates the interrupt (as STA VIC+$11 ; the line number is a 9-bit value, with bits 0-7 in VIC ; register $12) LDA #40 ; Set VIC register $11 to 40, so along with bit 7 of VIC STA VIC+$12 ; register $10, this sets the raster interrupt to be ; generated when the raster reaches line 40 LDA #%100 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore CLI ; Enable interrupts again JSR SWAPPZERO ; The call to KERNALSETUP above swapped the contents of ; zero page with the page at $CE00, to ensure the Kernal ; routines ran with their copy of zero page rather than ; the game's zero page ; ; We are done using the Kernal functions, so now we swap ; them back so the Kernal's zero page is moved to $CE00 ; again, ready for next time, and the game's zero page ; variables are once again set up, ready for the game ; code to use PLP ; Retrieve the processor flags that we stashed after the ; call to KERNALLOAD above CLI ; Enable interrupts to make sure the PHP doesn't disable ; interrupts (which it could feasibly do by restoring a ; set I flag) BCS tapeerror ; If KERNALLOAD returns with the C flag set then this ; indicates that a load error occurred, so jump to ; tapeerror to print either "TAPE ERROR" or "DISK ERROR" LDA TAP% ; If the first byte of the loaded file has bit 7 set, BMI ELT2F ; jump to ELT2F, as this is an invalid commander file ; ; ELT2F contains a BRK instruction, which will force an ; interrupt to call the address in BRKV, which will ; print out the system error at ELT2F LDY #NT% ; We have successfully loaded the commander file to the ; TAP% staging area, so now we want to copy it to the ; last saved commander data block at NA%+8, so we set up ; a counter in Y to copy NT% bytes .copyme LDA TAP%,Y ; Copy the Y-th byte of TAP% to the Y-th byte of NA%+8 STA NA%+8,Y DEY ; Decrement the loop counter BPL copyme ; Loop back until we have copied all NT% bytes .LOR SEC ; Set the C flag RTS ; Return from the subroutine .ELT2F LDA #9 ; Print extended token 9 ("{cr}{all caps}ILLEGAL ELITE JSR DETOK ; II FILE{sentence case}") JSR t ; Scan the keyboard until a key is pressed, returning ; the ASCII code in A and X JMP SVE ; Jump to SVE to display the disk access menu and return ; from the subroutine using a tail call ; ****************************************************************************** ; ; Name: backtonormal ; Type: Subroutine ; Category: Utility routines ; Summary: Disable the keyboard, set the SVN flag to 0, and return with A = 0 ; ; ------------------------------------------------------------------------------ ; ; This routine is unused in this version of Elite (it is left over from the ; 6502 Second Processor version). ; ; ****************************************************************************** .backtonormal RTS ; Return from the subroutine, as backtonormal does ; nothing in this version of Elite (it is left over from ; the 6502 Second Processor version) ; ****************************************************************************** ; ; Name: tapeerror ; Type: Subroutine ; Category: Save and load ; Summary: Print either "TAPE ERROR" or "DISK ERROR" ; ; ****************************************************************************** .tapeerror LDA #255 ; Print extended token 255 ("{cr}{currently selected JSR DETOK ; media} ERROR") JSR t ; Scan the keyboard until a key is pressed, returning ; the ASCII code in A and X JMP SVE ; Jump to SVE to display the disk access menu and return ; from the subroutine using a tail call ; ****************************************************************************** ; ; Name: CLDELAY ; Type: Subroutine ; Category: Utility routines ; Summary: Delay by iterating through 5 * 256 (1280) empty loops ; ; ------------------------------------------------------------------------------ ; ; This routine is unused in this version of Elite (it is left over from the ; 6502 Second Processor version). ; ; ****************************************************************************** .CLDELAY RTS ; Return from the subroutine, as CLDELAY does nothing in ; this version of Elite (it is left over from the 6502 ; Second Processor version) ; ****************************************************************************** ; ; Name: ZEKTRAN ; Type: Subroutine ; Category: Keyboard ; Summary: Clear the key logger ; ; ****************************************************************************** .ZEKTRAN LDX #64 ; We want to clear the 65 key logger locations from ; KEYLOOK to KEYLOOK+64, so set a counter in X LDA #0 ; We want to zero the key logger buffer, so set A % 0 STA thiskey ; Reset the value of thiskey in the key logger, which ; is used for logging keys that don't appear in the ; keyboard table .ZEKLOOP STA KEYLOOK,X ; Reset the X-th byte of the key logger buffer to 0 DEX ; Decrement the loop counter BPL ZEKLOOP ; Loop back until we have zeroed bytes #11 through #0 RTS ; Return from the subroutine RTS ; This instruction has no effect as we already returned ; from the subroutine ; ****************************************************************************** ; ; Name: SPS1 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Calculate the vector to the planet and store it in XX15 ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; SPS1+1 A BRK instruction ; ; ****************************************************************************** .SPS1 LDX #0 ; Copy the two high bytes of the planet's x-coordinate JSR SPS3 ; into K3(2 1 0), separating out the sign bit into K3+2 LDX #3 ; Copy the two high bytes of the planet's y-coordinate JSR SPS3 ; into K3(5 4 3), separating out the sign bit into K3+5 LDX #6 ; Copy the two high bytes of the planet's z-coordinate JSR SPS3 ; into K3(8 7 6), separating out the sign bit into K3+8 ; Fall through into TAS2 to build XX15 from K3 ; ****************************************************************************** ; ; Name: TAS2 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Normalise the three-coordinate vector in K3 ; ; ------------------------------------------------------------------------------ ; ; Normalise the vector in K3, which has 16-bit values and separate sign bits, ; and store the normalised version in XX15 as a signed 8-bit vector. ; ; A normalised vector (also known as a unit vector) has length 1, so this ; routine takes an existing vector in K3 and scales it so the length of the ; new vector is 1. This is used in a number of places: when drawing the compass, ; when applying AI tactics to ships (so traders fly towards planets and missiles ; fly towards their targets, for example), and when implementing the docking ; computer in the enhanced versions of Elite. ; ; We do this in two stages. This stage shifts the 16-bit vector coordinates in ; K3 to the left as far as they will go without losing any bits off the end, so ; we can then take the high bytes and use them as the most accurate 8-bit vector ; to normalise. Then the next stage (in routine NORM) does the normalisation. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; K3(2 1 0) The 16-bit x-coordinate as (x_sign x_hi x_lo), where ; x_sign is just bit 7 ; ; K3(5 4 3) The 16-bit y-coordinate as (y_sign y_hi y_lo), where ; y_sign is just bit 7 ; ; K3(8 7 6) The 16-bit z-coordinate as (z_sign z_hi z_lo), where ; z_sign is just bit 7 ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; XX15 The normalised vector, with: ; ; * The x-coordinate in XX15 ; ; * The y-coordinate in XX15+1 ; ; * The z-coordinate in XX15+2 ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; TA2 Calculate the length of the vector in XX15 (ignoring the ; low coordinates), returning it in Q ; ; ****************************************************************************** .TAS2 LDA K3 ; OR the three low bytes and 1 to get a byte that has ORA K3+3 ; a 1 wherever any of the three low bytes has a 1 ORA K3+6 ; (as well as always having bit 0 set), and store in ORA #1 ; K3+9 STA K3+9 LDA K3+1 ; OR the three high bytes to get a byte in A that has a ORA K3+4 ; 1 wherever any of the three high bytes has a 1 ORA K3+7 ; (A K3+9) now has a 1 wherever any of the 16-bit ; values in K3 has a 1 .TAL2 ASL K3+9 ; Shift (A K3+9) to the left, so bit 7 of the high byte ROL A ; goes into the C flag BCS TA2 ; If the left shift pushed a 1 out of the end, then we ; know that at least one of the coordinates has a 1 in ; this position, so jump to TA2 as we can't shift the ; values in K3 any further to the left ASL K3 ; Shift K3(1 0), the x-coordinate, to the left ROL K3+1 ASL K3+3 ; Shift K3(4 3), the y-coordinate, to the left ROL K3+4 ASL K3+6 ; Shift K3(6 7), the z-coordinate, to the left ROL K3+7 BCC TAL2 ; Jump back to TAL2 to do another shift left (this BCC ; is effectively a JMP as we know bit 7 of K3+7 is not a ; 1, as otherwise bit 7 of A would have been a 1 and we ; would have taken the BCS above) .TA2 LDA K3+1 ; Fetch the high byte of the x-coordinate from our left- LSR A ; shifted K3, shift it right to clear bit 7, stick the ORA K3+2 ; sign bit in there from the x_sign part of K3, and STA XX15 ; store the resulting signed 8-bit x-coordinate in XX15 LDA K3+4 ; Fetch the high byte of the y-coordinate from our left- LSR A ; shifted K3, shift it right to clear bit 7, stick the ORA K3+5 ; sign bit in there from the y_sign part of K3, and STA XX15+1 ; store the resulting signed 8-bit y-coordinate in ; XX15+1 LDA K3+7 ; Fetch the high byte of the z-coordinate from our left- LSR A ; shifted K3, shift it right to clear bit 7, stick the ORA K3+8 ; sign bit in there from the z_sign part of K3, and STA XX15+2 ; store the resulting signed 8-bit z-coordinate in ; XX15+2 ; Now we have a signed 8-bit version of the vector K3 in ; XX15, so fall through into NORM to normalise it ; ****************************************************************************** ; ; Name: NORM ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Normalise the three-coordinate vector in XX15 ; Deep dive: Tidying orthonormal vectors ; Orientation vectors ; ; ------------------------------------------------------------------------------ ; ; We do this by dividing each of the three coordinates by the length of the ; vector, which we can calculate using Pythagoras. Once normalised, 96 ($60) is ; used to represent a value of 1, and 96 with bit 7 set ($E0) is used to ; represent -1. This enables us to represent fractional values of less than 1 ; using integers. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; XX15 The vector to normalise, with: ; ; * The x-coordinate in XX15 ; ; * The y-coordinate in XX15+1 ; ; * The z-coordinate in XX15+2 ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; XX15 The normalised vector ; ; Q The length of the original XX15 vector ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; NO1 Contains an RTS ; ; ****************************************************************************** .NORM LDA XX15 ; Fetch the x-coordinate into A JSR SQUA ; Set (A P) = A * A = x^2 STA R ; Set (R Q) = (A P) = x^2 LDA P STA Q LDA XX15+1 ; Fetch the y-coordinate into A JSR SQUA ; Set (A P) = A * A = y^2 STA T ; Set (T P) = (A P) = y^2 LDA P ; Set (R Q) = (R Q) + (T P) = x^2 + y^2 ADC Q ; STA Q ; First, doing the low bytes, Q = Q + P LDA T ; And then the high bytes, R = R + T ADC R STA R LDA XX15+2 ; Fetch the z-coordinate into A JSR SQUA ; Set (A P) = A * A = z^2 STA T ; Set (T P) = (A P) = z^2 LDA P ; Set (R Q) = (R Q) + (T P) = x^2 + y^2 + z^2 ADC Q ; STA Q ; First, doing the low bytes, Q = Q + P LDA T ; And then the high bytes, R = R + T ADC R STA R JSR LL5 ; We now have the following: ; ; (R Q) = x^2 + y^2 + z^2 ; ; so we can call LL5 to use Pythagoras to get: ; ; Q = SQRT(R Q) ; = SQRT(x^2 + y^2 + z^2) ; ; So Q now contains the length of the vector (x, y, z), ; and we can normalise the vector by dividing each of ; the coordinates by this value, which we do by calling ; routine TIS2. TIS2 returns the divided figure, using ; 96 to represent 1 and 96 with bit 7 set for -1 LDA XX15 ; Call TIS2 to divide the x-coordinate in XX15 by Q, JSR TIS2 ; with 1 being represented by 96 STA XX15 LDA XX15+1 ; Call TIS2 to divide the y-coordinate in XX15+1 by Q, JSR TIS2 ; with 1 being represented by 96 STA XX15+1 LDA XX15+2 ; Call TIS2 to divide the z-coordinate in XX15+2 by Q, JSR TIS2 ; with 1 being represented by 96 STA XX15+2 .NO1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: KEYLOOK ; Type: Workspace ; Address: $8D0C to $8D52 ; Category: Keyboard ; Summary: The key logger ; Deep dive: Reading the Commodore 64 keyboard matrix ; ; ------------------------------------------------------------------------------ ; ; KEYLOOK (also known as KLO) is the Commodore 64 version's key logger. It does ; the same job as the KL key logger in the BBC Micro versions, but it has a very ; different structure, with one entry for every possible key press, rather than ; just one for each flight key. ; ; Specifically, it has one byte for each key in the Commodore 64 keyboard ; matrix, and it is laid out in the same order. The keyboard matrix is exposed ; to our code via port A on the CIA1 interface chip, through the memory-mapped ; locations $DC00 and $DC01. To read a key, you first set the column to scan by ; writing to $DC00, and the details of any key that is bring pressed in that ; column are returned in $DC01 (see the RDKEY routine for details). ; ; The keyboard matrix layout can be seen at https://sta.c64.org/cbm64kbdlay.html ; ; The KEYLOOK table mirrors the structure of the keyboard matrix, though it's ; reversed so that KEYLOOK maps to the keyboard matrix from the bottom corner of ; the above diagram, working right to left and down to up. (The RDKEY routine ; is responsible for filling the KEYLOOK table, and it chooses to work through ; the table in this direction). ; ; The RDKEY routine scans the keyboard matrix and sets each entry in KEYLOOK ; according to whether that key is being pressed. The entries that map to the ; flight keys have labels KY1 through KY7 for the main flight controls, and ; KY12 to KY20 for the secondary controls, so the main game code can check ; whether a key is being pressed by simply checking for non-zero values in the ; relevant KY entries. The order of the KY labels is strange because they are ; the same labels as in the BBC Micro version, and the order of the keys in ; the logger is completely different on the Commodore 64 (the labels are ordered ; from KY1 to KY7 and KY12 to KY20 in the BBC Micro version). ; ; The index of a key in the KEYLOOK table is referred to as the "internal key ; number" throughout this documentation, so the "@" key has an internal key ; number of 18 (or $12), for example, as it is stored at KEYLOOK+18. ; ; Note that the initial content of the KEYLOOK table is a simple repeated string ; of "123456789ABCDEF0", as this was used in the original source code to create ; the table during assembly. These initial values have no meaning. ; ; ****************************************************************************** .KEYLOOK SKIP 0 ; KEYLOOK and KLO share the same address .KLO EQUS "1" ; The key logger in the BBC Micro version has a spare ; byte at the start for storing the last key press, so ; we also include a spare byte here so the KLO logger ; in the Commodore 64 version behaves in a similar way ; to the KL key logger in the BBC Micro ; ; The Commodore 64 version doesn't use this byte, but ; instead it stores the last-pressed key in the KL and ; thi8skey variables EQUS "2" ; RUN/STOP is being pressed (KLO+$1) ; ; * 0 = no ; ; * Non-zero = yes EQUS "3" ; "Q" is being pressed (KLO+$2) ; ; * 0 = no ; ; * Non-zero = yes .KY12 EQUS "4" ; "C=" is being pressed (energy bomb, KLO+$3) ; ; * 0 = no ; ; * Non-zero = yes .KY2 EQUS "5" ; Space is being pressed (speed up, KLO+$4) ; ; * 0 = no ; ; * Non-zero = yes EQUS "6" ; "2" is being pressed (KLO+$5) ; ; * 0 = no ; ; * Non-zero = yes EQUS "7" ; CTRL is being pressed (KLO+$6) ; ; * 0 = no ; ; * Non-zero = yes .KY13 EQUS "8" ; Left arrow is being pressed (launch escape pod, ; KLO+$7) ; ; * 0 = no ; ; * Non-zero = yes EQUS "9" ; "1" is being pressed (KLO+$8) ; ; * 0 = no ; ; * Non-zero = yes .KY1 EQUS "A" ; "?" is being pressed (slow down, KLO+$9) ; ; * 0 = no ; ; * Non-zero = yes EQUS "B" ; Up arrow is being pressed (KLO+$A) ; ; * 0 = no ; ; * Non-zero = yes EQUS "C" ; "=" is being pressed (KLO+$B) ; ; * 0 = no ; ; * Non-zero = yes EQUS "D" ; Right SHIFT is being pressed (KLO+$C) ; ; * 0 = no ; ; * Non-zero = yes EQUS "E" ; CLR/HOME is being pressed (KLO+$D) ; ; * 0 = no ; ; * Non-zero = yes EQUS "F" ; ";" is being pressed (KLO+$E) ; ; * 0 = no ; ; * Non-zero = yes EQUS "0" ; "*" is being pressed (KLO+$F) ; ; * 0 = no ; ; * Non-zero = yes EQUS "1" ; "£" is being pressed (KLO+$10) ; ; * 0 = no ; ; * Non-zero = yes .KY3 EQUS "2" ; "<" is being pressed (roll left, KYO+$11) ; ; * 0 = no ; ; * Non-zero = yes EQUS "3" ; "@" is being pressed (KLO+$12) ; ; * 0 = no ; ; * Non-zero = yes EQUS "4" ; ":" is being pressed (KLO+$13) ; ; * 0 = no ; ; * Non-zero = yes .KY4 EQUS "5" ; ">" is being pressed (roll right, KLO+$14) ; ; * 0 = no ; ; * Non-zero = yes EQUS "6" ; "-" is being pressed (KLO+$15) ; ; * 0 = no ; ; * Non-zero = yes EQUS "7" ; "L" is being pressed (KLO+$16) ; ; * 0 = no ; ; * Non-zero = yes .KY20 EQUS "8" ; "P" is being pressed (deactivate docking computer, ; KLO+$17) ; ; * 0 = no ; ; * Non-zero = yes EQUS "9" ; "+" is being pressed (KLO+$18) ; ; * 0 = no ; ; * Non-zero = yes EQUS "A" ; "N" is being pressed (KLO+$19) ; ; * 0 = no ; ; * Non-zero = yes EQUS "B" ; "O" is being pressed (KLO+$1A) ; ; * 0 = no ; ; * Non-zero = yes EQUS "C" ; "K" is being pressed (KLO+$1B) ; ; * 0 = no ; ; * Non-zero = yes .KY16 EQUS "D" ; "M" is being pressed (fire missile, KLO+$1C) ; ; * 0 = no ; ; * Non-zero = yes EQUS "E" ; "0" is being pressed (KLO+$1D) ; ; * 0 = no ; ; * Non-zero = yes .KY18 EQUS "F" ; "J" is being pressed (in-system jump, KLO+$1E) ; ; * 0 = no ; ; * Non-zero = yes EQUS "0" ; "I" is being pressed (KLO+$1F) ; ; * 0 = no ; ; * Non-zero = yes EQUS "1" ; "9" is being pressed (KLO+$20) ; ; * 0 = no ; ; * Non-zero = yes EQUS "2" ; "V" is being pressed (KLO+$21) ; ; * 0 = no ; ; * Non-zero = yes .KY15 EQUS "3" ; "U" is being pressed (unarm missile, KLO+$22) ; ; * 0 = no ; ; * Non-zero = yes EQUS "4" ; "H" is being pressed (KLO+$23) ; ; * 0 = no ; ; * Non-zero = yes EQUS "5" ; "B" is being pressed (KLO+$24) ; ; * 0 = no ; ; * Non-zero = yes EQUS "6" ; "8" is being pressed (KLO+$25) ; ; * 0 = no ; ; * Non-zero = yes EQUS "7" ; "G" is being pressed (KLO+$26) ; ; * 0 = no ; ; * Non-zero = yes EQUS "8" ; "Y" is being pressed (KLO+$27) ; ; * 0 = no ; ; * Non-zero = yes EQUS "9" ; "7" is being pressed (KLO+$28) ; ; * 0 = no ; ; * Non-zero = yes .KY5 EQUS "A" ; "X" is being pressed (pull up, KLO+$29) ; ; * 0 = no ; ; * Non-zero = yes .KY14 EQUS "B" ; "T" is being pressed (target missile, KLO+$2A) ; ; * 0 = no ; ; * Non-zero = yes EQUS "C" ; "F" is being pressed (KLO+$2B) ; ; * 0 = no ; ; * Non-zero = yes .KY19 EQUS "D" ; "C" is being pressed (activate docking computer, ; KLO+$2C) ; ; * 0 = no ; ; * Non-zero = yes EQUS "E" ; "6" is being pressed (KLO+$2D) ; ; * 0 = no ; ; * Non-zero = yes EQUS "F" ; "D" is being pressed (KLO+$2E) ; ; * 0 = no ; ; * Non-zero = yes EQUS "0" ; "R" is being pressed (KLO+$2F) ; ; * 0 = no ; ; * Non-zero = yes EQUS "1" ; "5" is being pressed (KLO+$30) ; ; * 0 = no ; ; * Non-zero = yes EQUS "2" ; Left SHIFT is being pressed (KLO+$31) ; ; * 0 = no ; ; * Non-zero = yes .KY17 EQUS "3" ; "E" is being pressed (activate E.C.M., KLO+$32) ; ; * 0 = no ; ; * Non-zero = yes .KY6 EQUS "4" ; "S" is being pressed (pitch down, KLO+$33) ; ; * 0 = no ; ; * Non-zero = yes EQUS "5" ; "Z" is being pressed (KLO+$34) ; ; * 0 = no ; ; * Non-zero = yes EQUS "6" ; "4" is being pressed (KLO+$35) ; ; * 0 = no ; ; * Non-zero = yes .KY7 EQUS "7" ; "A" is being pressed (fire lasers, KLO+$36) ; ; * 0 = no ; ; * Non-zero = yes ; ; This is also set when the joystick fire button has ; been pressed EQUS "8" ; "W" is being pressed (KLO+$37) ; ; * 0 = no ; ; * Non-zero = yes EQUS "9" ; "3" is being pressed (KLO+$38) ; ; * 0 = no ; ; * Non-zero = yes EQUS "A" ; Cursor up/down is being pressed (KLO+$39) ; ; * 0 = no ; ; * Non-zero = yes EQUS "B" ; F5 is being pressed (KLO+$3A) ; ; * 0 = no ; ; * Non-zero = yes EQUS "C" ; F3 is being pressed (KLO+$3B) ; ; * 0 = no ; ; * Non-zero = yes EQUS "D" ; F1 is being pressed (KLO+$3C) ; ; * 0 = no ; ; * Non-zero = yes EQUS "E" ; F7 is being pressed (KLO+$3D) ; ; * 0 = no ; ; * Non-zero = yes EQUS "F" ; Cursor left/right is being pressed (KLO+$3E) ; ; * 0 = no ; ; * Non-zero = yes EQUS "0" ; RETURN is being pressed (KLO+$3F) ; ; * 0 = no ; ; * Non-zero = yes EQUS "1" ; INS/DEL is being pressed (KLO+$40) ; ; * 0 = no ; ; * Non-zero = yes EQUS "234567" ; These bytes appear to be unused ; ****************************************************************************** ; ; Name: RDKEY ; Type: Subroutine ; Category: Keyboard ; Summary: Scan the keyboard for key presses and the joystick, and update the ; key logger ; Deep dive: Reading the Commodore 64 keyboard matrix ; Sprite usage in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Y Y is preserved ; ; C flag The status of the result: ; ; * Clear if no keys are being pressed ; ; * Set if either a key is being pressed or the joystick ; fire button is being pressed ; ; ****************************************************************************** .RDKEY TYA ; Store Y on the stack so we can preserve it across PHA ; calls to this routine LDA #%101 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 1 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on ; ; See the memory map at the top of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore LDA VIC+$15 ; Clear bit 1 of VIC register $15 to disable sprite 1, AND #%11111101 ; so this removes the explosion sprite from the screen STA VIC+$15 ; if there is one (so that the explosion burst only ; appears fleetingly at the point of explosion, and ; doesn't linger too long) JSR ZEKTRAN ; Call ZEKTRAN to clear the key logger LDX JSTK ; IF JSTK = 0 then joysticks are not configured, so jump BEQ scanmatrix ; to scanmatrix to start scanning the keyboard matrix ; If joysticks are configured then JSTK = $FF, so X is ; now set to this value LDA CIA ; Set A to bits 0-4 of CIA1 register 0, which are set to AND #%00011111 ; the following: ; ; * Bit 0 = port 2 joystick up pressed ; ; * Bit 1 = port 2 joystick down pressed ; ; * Bit 2 = port 2 joystick left pressed ; ; * Bit 3 = port 2 joystick right pressed ; ; * Bit 4 = port 2 joystick fire pressed ; ; A clear bit indicates that the direction/button is ; being pressed, while a set bit indicates that it isn't IF _GMA_RELEASE EOR #%00011111 ; Flip the polarity of bits 0-4, so a set bit indicates BNE dojoystick ; activity, and jump to dojoystick with X = $FF if any ; of the bits are set ELIF _SOURCE_DISK CMP #%00011111 ; If nothing is being pressed then A will be %00011111, BNE dojoystick ; in which case keep going, otherwise something is being ; pressed, so jump to dojoystick with X = $FF ; ; This relies on the top three bits of CIA1 register 0 ; always being zero, which is probably why this test was ; rewritten for the GMA releases ENDIF .scanmatrix ; If we get here then we need to scan the keyboard ; matrix CLC ; Clear the C flag, so we can return this if no keys are ; being pressed LDX #0 ; Set X = 0 so we select every column in the keyboard ; matrix (so we can quickly check whether any keys are ; being held down) SEI ; Disable interrupts while we read the keyboard matrix STX $DC00 ; Set $DC00 = 0 to select every column in the keyboard ; matrix LDX $DC01 ; Read $DC01 to see whether any keys are being pressed ; in the columns we specified in $DC00 (i.e. in any ; columns) CLI ; Enable interrupts again INX ; If we read $FF from $DC01 then this indicates that no BEQ nokeys2 ; keys are being pressed in any columns in the keyboard ; matrix (as a pressed key is indicated by a clear bit ; for that column, and incrementing $FF gives us zero), ; so this jumps to nokeys2 if there are no keys being ; pressed, which will return from the subroutine with ; the C flag clear ; If we get here then something is being pressed on the ; keyboard, so we now loop through the whole matrix and ; populate the key logger LDX #$40 ; The key logger at KEYLOOK records key presses for each ; of the keys on the keyboard, from KEYLOOK+$1 to ; KEYLOOK+$40, so set a counter in X to work through the ; whole keyboard matrix LDA #%11111110 ; We can check whether anything is being pressed in a ; particular column in the keyboard matrix by clearing ; the corresponding bit in $DC00, so set a byte in A ; with one bit clear, so we can shift it along to work ; through the keyboard matrix from column 0 to 7 .Rdi1 SEI ; Disable interrupts while we read the keyboard matrix STA $DC00 ; Set $DC00 = A to select the column in the keyboard ; matrix that corresponds to the clear bit in A (so we ; start with column 0 and end with to column 7) PHA ; Store our column selector byte on the stack, so we can ; retrieve it for the next iteration around the loop LDY #8 ; Each column contains eight keys, one on each row of ; the keyboard matrix, so we now need to work our way ; through each row, checking to see if the key in that ; row has been pressed, so set a row counter in Y .Rdi0 LDA $DC01 ; Read $DC01 to see whether any keys are being pressed ; in the column we specified in $DC00 CMP $DC01 ; Keep reading the value from $DC01 until it is stable BNE Rdi0 ; for the duration of the LDA and CMP instructions, so ; we know we have a clean signal (this implements a ; simple "debounce", which is the act of delaying the ; effects of a button press to ensure that the action ; is only performed once rather than repeatedly) CLI ; Enable interrupts again ; We now have a result from the keyboard scan that will ; have a 0 in bit x if the key in row x is being ; pressed in the current column, so we need to loop ; through all eight bits in A to determine which keys ; are being pressed .Rdi2 LSR A ; Shift bit 0 of A into the C flag BCS Rdi3 ; If the bit is set then the key corresponding to this ; row is not being pressed, so jump to Rdi3 to leave the ; key logger entry for this key set to 0 DEC KEYLOOK,X ; Decrement the X-th entry in the key logger from 0 to ; $FF to indicate that this key is being pressed STX thiskey ; Store the value of X (which we're calling the internal ; key number in this commentary) in thiskey, which ; stores the number of the last key pressed SEC ; Set the C flag, so we can rotate it into the column ; selector byte below .Rdi3 DEX ; Decrement the key logger counter in X, so we move on ; to the next key BMI Rdiex ; If we have just decremented X past zero then we have ; processed all keys and have filled the whole key ; logger, so jump to Rdiex to finish up and return from ; the subroutine DEY ; Otherwise decrement the row counter in Y to check the ; next row in the current column BNE Rdi2 ; Loop back to Rdi2 to check the next key, until we have ; done all eight keys in this column PLA ; We now want to move on to the next column, so fetch ; the column selector byte from the stack, so we can ; move on to the next iteration around the loop ROL A ; Rotate the value in A to the left to move the clear ; bit along by one place, so we can select the next ; column ; ; Note that the ROL instruction rotates the C flag into ; bit 0 of A, and the C flag is set if we just detected ; a key press in the last column, so this ensures we ; don't select columns that we know contain key presses ; (but it does allow scans of columns that we know are ; not being pressed, though these won't affect the ; result of other column scans, as the scan only detects ; key presses that pull the matrix low) BNE Rdi1 ; Jump to Rdi1 to move on to the next column ; ; This BNE is effectively a JMP as A will never be zero ; by this point; the only way it could happen is if ; eight zeroes were rotated into A, but we know that at ; least one of those loops much have a key press as we ; already scanned the whole matrix at the start, so this ; can't happen .Rdiex PLA ; We put the column selector byte on the stack in the ; loop above, so make sure we remove it to prevent the ; stack from filling up SEC ; Set the C flag to return from the subroutine, to ; indicate that a key has been pressed .nokeys2 LDA #%01111111 ; Set bits 0 to 5 of $DC00 to deselect every column in STA $DC00 ; the keyboard matrix BNE nojoyst ; Jump to nojoyst to skip the joystick code (this BNE is ; effectively a JMP as A is never zero) .dojoystick ; If we get here then at least one of the joystick ; controls has been pressed: ; ; * Bit 0 = port 2 joystick up pressed ; ; * Bit 1 = port 2 joystick down pressed ; ; * Bit 2 = port 2 joystick left pressed ; ; * Bit 3 = port 2 joystick right pressed ; ; * Bit 4 = port 2 joystick fire pressed IF _GMA_RELEASE ; A set bit in A indicates that the direction/button ; is being pressed, while a clear bit indicates that it ; isn't ; ; X has the value $FF at this point LSR A ; Shift bit 0 into the C flag, and if it is set, store BCC downj ; $FF in KY6 to indicate the joystick is pointing up STX KY6 .downj LSR A ; Shift bit 1 into the C flag, and if it is set, store BCC upj ; $FF in KY5 to indicate the joystick is pointing down STX KY5 .upj LSR A ; Shift bit 2 into the C flag, and if it is set, store BCC leftj ; $FF in KY3 to indicate the joystick is pointing left STX KY3 .leftj LSR A ; Shift bit 3 into the C flag, and if it is set, store BCC rightj ; $FF in KY4 to indicate the joystick is pointing right STX KY4 .rightj LSR A ; Shift bit 4 into the C flag, and if it is set, store BCC firej ; $FF in KY7 to indicate the joystick fire button is STX KY7 ; being pressed .firej ; If we get here then the C flag is set if the joystick ; fire button is being pressed, or clear otherwise ELIF _SOURCE_DISK ; A clear bit in A indicates that the direction/button ; is being pressed, while a set bit indicates that it ; isn't ; ; X has the value $FF at this point LSR A ; Shift bit 0 into the C flag, and if it is clear, store BCS downj ; $FF in KY6 to indicate the joystick is pointing up STX KY6 .downj LSR A ; Shift bit 1 into the C flag, and if it is clear, store BCS upj ; $FF in KY5 to indicate the joystick is pointing down STX KY5 .upj LSR A ; Shift bit 2 into the C flag, and if it is clear, store BCS leftj ; $FF in KY3 to indicate the joystick is pointing left STX KY3 .leftj LSR A ; Shift bit 3 into the C flag, and if it is clear, store BCS rightj ; $FF in KY4 to indicate the joystick is pointing right STX KY4 .rightj LSR A ; Shift bit 4 into the C flag, and if it is clear, store BCS firej ; $FF in KY7 to indicate the joystick fire button is STX KY7 ; being pressed ; If we get here then the joystick fire button is being ; pressed, so the C flag is clear EQUB $24 ; Skip the next instruction by turning it into $24 $18, ; or BIT $0018, which does nothing apart from affect the ; flags ; ; This doesn't make a lot of sense as the next ; instruction is a CLC, and the C flag is already clear, ; so this has no effect; perhaps the next instruction is ; supposed to be a SEC, but as this part of the code was ; rewritten for the GMA release, it's all a bit moot .firej CLC ; Clear the C flag to indicate that no buttons are being ; pressed ENDIF LDA JSTGY ; If JSTGY is 0 then the game is not configured to BEQ noswapys ; reverse the controller y-axis, so jump to noswapys to ; skip the following and leave the joystick direction ; alone LDA KY5 ; Swap the values of KY5 and KY6, which are the two LDX KY6 ; y-axis directions (i.e. up and down) STA KY6 STX KY5 .noswapys LDA JSTE ; JSTE contains $FF if both joystick channels are BEQ noswapxs ; reversed and 0 otherwise, so skip to noswapxs if the ; joystick channels are not reversed LDA KY5 ; Swap the values of KY5 and KY6, which are the two LDX KY6 ; y-axis directions (i.e. up and down) STA KY6 STX KY5 LDA KY3 ; Swap the values of KY3 and KY4, which are the two LDX KY4 ; x-axis directions (i.e. right and left) STA KY4 STX KY3 .noswapxs .nojoyst LDA QQ11 ; If QQ11 = 0 then this is the space view, so jump to BEQ allkeys ; allkeys to skip resetting the secondary flight ; controls in the key logger LDA #0 ; This is not the space view, so reset the entries in STA KY12 ; the key logger for the secondary flight controls from STA KY13 ; KY12 to KY20, as these keys only have meaning in the STA KY14 ; space view STA KY15 STA KY16 STA KY17 STA KY18 STA KY19 STA KY20 .allkeys LDA #%100 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore PLA ; Retrieve the value of Y from the stack, which we TAY ; stored at the start of the subroutine, so the value of ; Y is preserved LDA thiskey ; Set A and X to the internal key number of the last key TAX ; that we scanned, so it is set as the last key pressed RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: WARP ; Type: Subroutine ; Category: Flight ; Summary: Perform an in-system jump ; Deep dive: A sense of scale ; ; ------------------------------------------------------------------------------ ; ; This is called when we press "J" during flight. The following checks are ; performed: ; ; * Make sure we don't have any ships or space stations in the vicinity ; ; * Make sure we are not in witchspace ; ; * If we are facing the planet, make sure we aren't too close ; ; * If we are facing the sun, make sure we aren't too close ; ; If the above checks are passed, then we perform an in-system jump by moving ; the sun and planet in the opposite direction to travel, so we appear to jump ; in space. This means that any asteroids, cargo canisters or escape pods get ; dragged along for the ride. ; ; ****************************************************************************** .WARP LDX JUNK ; Set X to the total number of junk items in the ; vicinity (e.g. asteroids, escape pods, cargo ; canisters, Shuttles, Transporters and so on) LDA FRIN+2,X ; If the slot at FRIN+2+X is non-zero, then we have ; something else in the vicinity besides asteroids, ; escape pods and cargo canisters, so to check whether ; we can jump, we first grab the slot contents into A ORA SSPR ; If there is a space station nearby, then SSPR will ; be non-zero, so OR'ing with SSPR will produce a ; non-zero result if either A or SSPR are non-zero ORA MJ ; If we are in witchspace, then MJ will be non-zero, so ; OR'ing with MJ will produce a non-zero result if ; either A or SSPR or MJ are non-zero BNE WA1 ; A is non-zero if we have either a ship or a space ; station in the vicinity, or we are in witchspace, in ; which case jump to WA1 to make a low beep to show that ; we can't do an in-system jump LDY K%+8 ; Otherwise we can do an in-system jump, so now we fetch ; the byte at K%+8, which contains the z_sign for the ; first ship slot, i.e. the distance of the planet BMI WA3 ; If the planet's z_sign is negative, then the planet ; is behind us, so jump to WA3 to skip the following TAY ; Set A = Y = 0 (as we didn't BNE above) so the call ; to MAS2 measures the distance to the planet JSR MAS2 ; Call MAS2 to set A to the largest distance to the ; planet in any of the three axes (we could also call ; routine m to do the same thing, as A = 0) CMP #2 ; If A < 2 then jump to WA1 to abort the in-system jump BCC WA1 ; with a low beep, as we are facing the planet and are ; too close to jump in that direction .WA3 LDY K%+NI%+8 ; Fetch the z_sign (byte #8) of the second ship in the ; ship data workspace at K%, which is reserved for the ; sun or the space station (in this case it's the ; former, as we already confirmed there isn't a space ; station in the vicinity) BMI WA2 ; If the sun's z_sign is negative, then the sun is ; behind us, so jump to WA2 to skip the following LDY #NI% ; Set Y to point to the offset of the ship data block ; for the sun, which is NI% (as each block is NI% bytes ; long, and the sun is the second block) JSR m ; Call m to set A to the largest distance to the sun ; in any of the three axes CMP #2 ; If A < 2 then jump to WA1 to abort the in-system jump BCC WA1 ; with a low beep, as we are facing the sun and are too ; close to jump in that direction .WA2 ; If we get here, then we can do an in-system jump, as ; we don't have any ships or space stations in the ; vicinity, we are not in witchspace, and if we are ; facing the planet or the sun, we aren't too close to ; jump towards it ; ; We do an in-system jump by moving the sun and planet, ; rather than moving our own local bubble (this is why ; in-system jumps drag asteroids, cargo canisters and ; escape pods along for the ride). Specifically, we move ; them in the z-axis by a fixed amount in the opposite ; direction to travel, thus performing a jump towards ; our destination LDA #$81 ; Set R = R = P = $81 STA S STA R STA P LDA K%+8 ; Set A = z_sign for the planet JSR ADD ; Set (A X) = (A P) + (S R) ; = (z_sign $81) + $8181 ; = (z_sign $81) - $0181 ; ; This moves the planet against the direction of travel ; by reducing z_sign by 1, as the above maths is: ; ; z_sign 00000000 ; + 00000000 10000001 ; - 00000001 10000001 ; ; or: ; ; z_sign 00000000 ; + 00000000 00000000 ; - 00000001 00000000 ; ; i.e. the high byte is z_sign - 1, making sure the sign ; is preserved STA K%+8 ; Set the planet's z_sign to the high byte of the result LDA K%+NI%+8 ; Set A = z_sign for the sun JSR ADD ; Set (A X) = (A P) + (S R) ; = (z_sign $81) + $8181 ; = (z_sign $81) - $0181 ; ; which moves the sun against the direction of travel ; by reducing z_sign by 1 STA K%+NI%+8 ; Set the planet's z_sign to the high byte of the result LDA #1 ; Temporarily set the view type to a non-zero value, so STA QQ11 ; the call to LOOK1 below clears the screen before ; switching to the space view STA MCNT ; Set the main loop counter to 1, so the next iteration ; through the main loop will potentially spawn ships ; (see part 2 of the main game loop at me3) LSR A ; Set EV, the extra vessels spawning counter, to 0 STA EV ; (the LSR produces a 0 as A was previously 1) LDX VIEW ; Set X to the current view (front, rear, left or right) JMP LOOK1 ; and jump to LOOK1 to initialise that view, returning ; from the subroutine using a tail call .WA1 LDY #sfxboop ; If we get here then we can't do an in-system jump, so JMP NOISE ; call the NOISE routine with Y = sfxboop to make a ; long, low beep and return from the subroutine using a ; tail call ; ****************************************************************************** ; ; Name: KYTB ; Type: Variable ; Category: Keyboard ; Summary: Lookup table for in-flight keyboard controls ; Deep dive: The key logger ; ; ------------------------------------------------------------------------------ ; ; This table is not used by the Commodore 64 version of Elite, and is left over ; from the BBC Micro version. ; ; ****************************************************************************** .KYTB RTS ; Return from the subroutine (used as an entry point and ; a fall-through from above) ; These are the primary flight controls (pitch, roll, ; speed and lasers): EQUB $68 + 128 ; ? KYTB+1 Slow down EQUB $62 + 128 ; Space KYTB+2 Speed up EQUB $66 + 128 ; < KYTB+3 Roll left EQUB $67 + 128 ; > KYTB+4 Roll right EQUB $42 + 128 ; X KYTB+5 Pull up EQUB $51 + 128 ; S KYTB+6 Pitch down EQUB $41 + 128 ; A KYTB+7 Fire lasers ; These are the secondary flight controls: EQUB $60 ; TAB KYTB+8 Energy bomb EQUB $70 ; ESCAPE KYTB+9 Launch escape pod EQUB $23 ; T KYTB+10 Target missile EQUB $35 ; U KYTB+11 Unarm missile EQUB $65 ; M KYTB+12 Fire missile EQUB $22 ; E KYTB+13 E.C.M. EQUB $45 ; J KYTB+14 In-system jump EQUB $52 ; C KYTB+15 Docking computer EQUB $37 ; P KYTB+16 Cancel docking computer ; ****************************************************************************** ; ; Name: CTRL ; Type: Subroutine ; Category: Keyboard ; Summary: Scan the keyboard to see if CTRL is currently pressed ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; X X = %10000001 (i.e. 129 or -127) if CTRL is being ; pressed ; ; X = 1 if CTRL is not being pressed ; ; A Contains the same as X ; ; ****************************************************************************** .CTRL LDX #6 ; Set X to the internal key number for CTRL and fall ; through into DKS4 to fetch the relevant entry from ; the key logger ; ****************************************************************************** ; ; Name: DKS4 ; Type: Subroutine ; Category: Keyboard ; Summary: Scan the keyboard to see if a specific key is being pressed ; Deep dive: The key logger ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The internal number of the key to check ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A $FF if the key is being pressed, 0 otherwise ; ; X Contains the same as A ; ; ****************************************************************************** .DKS4 LDA KEYLOOK,X ; Fetch the entry from the key logger for the key in X TAX ; Copy A to X RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DKSANYKEY ; Type: Subroutine ; Category: Keyboard ; Summary: An unused routine that scans a specific column in the keyboard ; matrix for a key press ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The value to pass to $DC00 (i.e. set all bits apart from ; one clear bit in the position of the column that we want ; to scan) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A The result: ; ; * $FF if a key is being pressed in the specified ; column ; ; * 0 if no key is being pressed in the specified ; column ; ; X Contains the same as A ; ; ****************************************************************************** .DKSANYKEY LDA #%101 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 1 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on ; ; See the memory map at the top of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore SEI ; Disable interrupts while we read the keyboard matrix STX $DC00 ; Set $DC00 = X to select the column in the keyboard ; matrix that corresponds to the clear bit in X LDX $DC01 ; Read $DC01 to see whether any keys are being pressed ; in the column we specified in $DC00 CLI ; Enable interrupts again INX ; If we read $FF from $DC01 then this indicates that no BEQ DKSL1 ; keys are being pressed in the specified column in the ; keyboard matrix (as a pressed key is indicated by a ; clear bit for that column, and incrementing $FF gives ; us zero), so this jumps to DKSL1 with X = 0 if there ; are no keys being pressed in that column LDX #$FF ; If we get here then something is being pressed on the ; keyboard in the specified column, so set X = $FF .DKSL1 LDA #%100 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore TXA ; Copy the result from X into A RTS ; Return from the subroutine RTS ; This instruction has no effect, as we already returned ; from the subroutine ; ****************************************************************************** ; ; Name: DKS2 ; Type: Subroutine ; Category: Keyboard ; Summary: Read the joystick position ; ; ------------------------------------------------------------------------------ ; ; This routine is not used in this version of Elite. It is left over from the ; BBC Micro version. ; ; ****************************************************************************** .DKS2 LDA KTRAN+7,X ; Fetch either the joystick X value or joystick Y value ; from the key logger buffer, depending on the value of ; X (i.e. fetch either KTRAN+8 or KTRAN+0) EOR JSTE ; The high byte A is now EOR'd with the value in ; location JSTE, which contains $FF if both joystick ; channels are reversed and 0 otherwise (so A now ; contains the high byte but inverted, if that's what ; the current settings say) RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DKS3 ; Type: Subroutine ; Category: Keyboard ; Summary: Toggle a configuration setting and emit a beep ; ; ------------------------------------------------------------------------------ ; ; This is called when the game is paused and a key is pressed that changes the ; game's configuration. ; ; Specifically, this routine toggles the configuration settings for the ; following keys: ; ; * RUN/STOP toggles keyboard flight damping (0) ; * A toggles keyboard auto-recentre (1) ; * X toggles author names on start-up screen (2) ; * F toggles flashing console bars (3) ; * Y toggles reverse joystick Y channel (4) ; * J toggles reverse both joystick channels (5) ; * K toggles keyboard and joystick (6) ; * M toggles docking music (7) ; * T toggles current media between tape and disk (8) ; * P toggles planetary details (9) ; * C toggles whether docking music can be toggled (10) ; * E swaps the docking and title music (11) ; * B toggles whether sounds are played during music (12) ; ; The numbers in brackets are the configuration options that we pass in Y. We ; pass the ASCII code of the key that has been pressed in X, and the option to ; check it against in Y, so this routine is typically called in a loop that ; loops through the various configuration option. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The internal number of the key that's been pressed ; ; Y The number of the configuration option to check against ; from the list above (i.e. Y must be from 0 to 12) ; ; ****************************************************************************** .DKS3 TXA ; Copy the ASCII code of the key that has been pressed ; into A CMP TGINT,Y ; If the pressed key doesn't match the configuration key BNE Dk3 ; for option Y (as listed in the TGINT table), then jump ; to Dk3 to return from the subroutine LDA DAMP,Y ; The configuration keys listed in TGINT correspond to EOR #$FF ; the configuration option settings from DAMP onwards, STA DAMP,Y ; so to toggle a setting, we fetch the existing byte ; from DAMP+Y, invert it and put it back (0 means no ; and $FF means yes in the configuration bytes, so ; this toggles the setting) JSR BELL ; Make a beep sound so we know something has happened TYA ; Store Y and A on the stack so we can retrieve them PHA ; below LDY #20 ; Wait for 20/50 of a second (0.4 seconds) on PAL JSR DELAY ; systems, or 20/60 of a second (0.33 seconds) on NTSC PLA ; Restore A and Y from the stack TAY .Dk3 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DKJ1 ; Type: Subroutine ; Category: Keyboard ; Summary: Read joystick and flight controls ; ; ------------------------------------------------------------------------------ ; ; Specifically, scan the keyboard for the speed up and slow down keys, and read ; the joystick's fire button and X and Y axes, storing the results in the key ; logger and the joystick position variables. ; ; This routine is only called if joysticks are enabled (JSTK = non-zero). ; ; ****************************************************************************** ;.DKJ1 ; These instructions are commented out in the original ; ; source (they are from the BBC Micro version) ;LDA auto ;BNE auton ; ;LDA KTRAN+1 ;STA KL+1 ;LDA KTRAN+2 ;STA KL+2 ; ;.BS1 ; ;LDA KTRAN+12 ;TAX ;AND #16 ;EOR #16 ;STA KL+7 ; ;LDX #1 ;JSR DKS2 ; ;ORA #1 ;STA JSTX ; ;LDX #2 ;JSR DKS2 ; ;EOR JSTGY ;STA JSTY ; ;JMP DK4 ; ****************************************************************************** ; ; Name: U% ; Type: Subroutine ; Category: Keyboard ; Summary: Clear the key logger and reset a number of flight variables ; ; ------------------------------------------------------------------------------ ; ; This routine zeroes the 17 key logger locations from KY1 to KY20 and the key ; variable at KL, and resets the 40 variable bytes from LSP to TYPE. ; ; Returns: ; ; A A is set to 0 ; ; Y Y is set to 0 ; ; ****************************************************************************** .U% LDA #0 ; Set A to 0, as this means "key not pressed" in the ; key logger at KLO LDY #56 ; We want to clear the 16 key logger locations from KY1 ; to KY20, and we want to zero the 40 variable bytes ; from LSP to TYPE, so set a counter in Y .DKL3 STA KLO,Y ; Store 0 in the Y-th byte of the key logger DEY ; Decrement the counter BNE DKL3 ; And loop back for the next key, until we have just ; cleared KLO+1 STA KL ; Clear KL, which is used for logging keys that don't ; appear in the keyboard table RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DOKEY ; Type: Subroutine ; Category: Keyboard ; Summary: Scan for the seven primary flight controls and apply the docking ; computer manoeuvring code ; Deep dive: The key logger ; The docking computer ; ; ------------------------------------------------------------------------------ ; ; Scan for the seven primary flight controls (or the equivalent on joystick), ; pause and configuration keys, and secondary flight controls, and update the ; key logger accordingly. Specifically, this part clears the key logger and ; updates it for the seven primary flight controls, and updates the pitch and ; roll rates accordingly. ; ; We then end up at DK4 to scan for other keys, beyond the seven primary flight ; controls. ; ; ****************************************************************************** .DOKEY JSR RDKEY ; Scan the keyboard for a key press and return the ; internal key number in A and X (or 0 for no key press) ;JSR U% ; These instructions are commented out in the original ; ; source ;JMP DK15 LDA auto ; If auto is 0, then the docking computer is not BEQ DK15 ; currently activated, so jump to DK15 to skip the ; docking computer manoeuvring code below .auton JSR ZINF ; Call ZINF to reset the INWK ship workspace LDA #96 ; Set nosev_z_hi = 96 STA INWK+14 ORA #%10000000 ; Set sidev_x_hi = -96 STA INWK+22 STA TYPE ; Set the ship type to -96, so the negative value will ; let us check in the DOCKIT routine whether this is our ; ship that is activating its docking computer, rather ; than an NPC ship docking LDA DELTA ; Set the ship speed to DELTA (our speed) STA INWK+27 JSR DOCKIT ; Call DOCKIT to calculate the docking computer's moves ; and update INWK with the results ; We now "press" the relevant flight keys, depending on ; the results from DOCKIT, starting with the pitch keys LDA INWK+27 ; Fetch the updated ship speed from byte #27 into A CMP #22 ; If A < 22, skip the next instruction BCC P%+4 LDA #22 ; Set A = 22, so the maximum speed during docking is 22 STA DELTA ; Update DELTA to the new value in A LDA #$FF ; Set A = $FF, which we can insert into the key logger ; to "fake" the docking computer working the keyboard LDX #(KY1-KLO) ; Set X to the offset of KY1 within the KLO table, so we ; "press" KY1 below ("?", slow down) LDY INWK+28 ; If the updated acceleration in byte #28 is zero, skip BEQ DK11 ; to DK11 BMI P%+4 ; If the updated acceleration is negative, skip the ; following instruction LDX #(KY2-KLO) ; Set X to the offset of KY2 within the KLO table, so we ; "press" KY2 with the next instruction (Space, speed ; up) STA KLO,X ; Store $FF in either KY1 or KY2 to "press" the relevant ; key, depending on whether the updated acceleration is ; negative (in which case we "press" KY1, "?", to slow ; down) or positive (in which case we "press" KY2, ; Space, to speed up) .DK11 ; We now "press" the relevant roll keys, depending on ; the results from DOCKIT LDA #128 ; Set A = 128, which indicates no change in roll when ; stored in JSTX (i.e. the centre of the roll indicator) LDX #(KY3-KLO) ; Set X to the offset of KY3 within the KLO table, so we ; "press" KY3 below ("<", increase roll) ASL INWK+29 ; Shift ship byte #29 left, which shifts bit 7 of the ; updated roll counter (i.e. the roll direction) into ; the C flag BEQ DK12 ; If the remains of byte #29 is zero, then the updated ; roll counter is zero, so jump to DK12 set JSTX to 128, ; to indicate there's no change in the roll BCC P%+4 ; If the C flag is clear, skip the following instruction LDX #(KY4-KLO) ; Set X to the offset of KY4 within the KLO table, so we ; "press" KY4 below (">", decrease roll) BIT INWK+29 ; We shifted the updated roll counter to the left above, BPL DK14 ; so this tests bit 6 of the original value, and if it ; is clear (i.e. the magnitude is less than 64), jump to ; DK14 to "press" the key and leave JSTX unchanged LDA #64 ; The magnitude of the updated roll is 64 or more, so STA JSTX ; set JSTX to 64 (so the roll decreases at half the ; maximum rate) LDA #0 ; And set A = 0 so we do not "press" any keys (so if the ; docking computer needs to make a serious roll, it does ; so by setting JSTX directly rather than by "pressing" ; a key) .DK14 STA KLO,X ; Store A in either KY3 or KY4, depending on whether ; the updated roll rate is increasing (KY3) or ; decreasing (KY4) LDA JSTX ; Fetch A from JSTX so the next instruction has no ; effect .DK12 STA JSTX ; Store A in JSTX to update the current roll rate ; We now "press" the relevant pitch keys, depending on ; the results from DOCKIT LDA #128 ; Set A = 128, which indicates no change in pitch when ; stored in JSTX (i.e. the centre of the pitch ; indicator) LDX #(KY5-KLO) ; Set X to the offset of KY5 within the KLO table, so we ; "press" KY5 below ("X", decrease pitch, pulling the ; nose up) ASL INWK+30 ; Shift ship byte #30 left, which shifts bit 7 of the ; updated pitch counter (i.e. the pitch direction) into ; the C flag BEQ DK13 ; If the remains of byte #30 is zero, then the updated ; pitch counter is zero, so jump to DK13 set JSTY to ; 128, to indicate there's no change in the pitch BCS P%+4 ; If the C flag is set, skip the following instruction LDX #(KY6-KLO) ; Set X to the offset of KY6 within the KLO table, so we ; "press" KY6 below ("S", increase pitch, so the nose ; dives) STA KLO,X ; Store 128 in either KY5 or KY6 to "press" the relevant ; key, depending on whether the pitch direction is ; negative (in which case we "press" KY5, "X", to ; decrease the pitch, pulling the nose up) or positive ; (in which case we "press" KY6, "S", to increase the ; pitch, pushing the nose down) LDA JSTY ; Fetch A from JSTY so the next instruction has no ; effect .DK13 STA JSTY ; Store A in JSTY to update the current pitch rate .DK15 LDX JSTX ; Set X = JSTX, the current roll rate (as shown in the ; RL indicator on the dashboard) LDA #14 ; Set A to 14, which is the amount we want to alter the ; roll rate by if the roll keys are being pressed LDY KY3 ; If the "<" key is not being pressed, skip the next BEQ P%+5 ; instruction JSR BUMP2 ; The "<" key is being pressed, so call the BUMP2 ; routine to increase the roll rate in X by A LDY KY4 ; If the ">" key is not being pressed, skip the next BEQ P%+5 ; instruction JSR REDU2 ; The "<" key is being pressed, so call the REDU2 ; routine to decrease the roll rate in X by A, taking ; the keyboard auto re-centre setting into account STX JSTX ; Store the updated roll rate in JSTX ;ASL A ; This instruction is commented out in the original ; source LDX JSTY ; Set X = JSTY, the current pitch rate (as shown in the ; DC indicator on the dashboard) LDY KY5 ; If the "X" key is not being pressed, skip the next BEQ P%+5 ; instruction JSR REDU2 ; The "X" key is being pressed, so call the REDU2 ; routine to decrease the pitch rate in X by A, taking ; the keyboard auto re-centre setting into account LDY KY6 ; If the "S" key is not being pressed, skip the next BEQ P%+5 ; instruction JSR BUMP2 ; The "S" key is being pressed, so call the BUMP2 ; routine to increase the pitch rate in X by A STX JSTY ; Store the updated roll rate in JSTY LDA JSTK ; If JSTK is zero, then we are configured to use the BEQ ant ; keyboard rather than the joystick, so jump to ant to ; skip the following LDA auto ; If the docking computer is currently activated, jump BNE ant ; to ant to skip the following ; If we get here then the joystick is configured and the ; docking computer is not activated, so we now centre ; the roll and pitch rates if required LDX #128 ; Set X to 128, which is the centre value for roll and ; pitch, and represents no change to the current roll or ; pitch LDA KY3 ; If the joystick is being moved left or right, jump to ORA KY4 ; termite to skip the following instruction BNE termite STX JSTX ; Set JSTX = 128 to set the roll rate to zero .termite LDA KY5 ; If the joystick is being moved up or down, jump to ant ORA KY6 ; to skip the following instruction, so pressing buttons BNE ant ; on the controller overrides the docking computer STX JSTY ; Set JSTX = 128 to set the pitch rate to zero .ant ; Fall through into DK4 to scan for other keys ; ****************************************************************************** ; ; Name: DK4 ; Type: Subroutine ; Category: Keyboard ; Summary: Scan for pause, configuration and secondary flight keys ; Deep dive: The key logger ; ; ------------------------------------------------------------------------------ ; ; Scan for pause and configuration keys, and if this is a space view, also scan ; for secondary flight controls. ; ; Specifically: ; ; * Scan for the pause button (INST/DEL) and if it's pressed, pause the game ; and process any configuration key presses until the game is unpaused ; (CLR/HOME) ; ; * If this is a space view, scan for secondary flight keys and update the ; relevant bytes in the key logger ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; FREEZE Rejoin the pause routine after processing a screen save ; ; ****************************************************************************** .DK4 LDX thiskey ; Fetch the key pressed from thiskey in the key logger STX KL ; Store X in KL, which is used to store the value of the ; last key pressed CPX #$40 ; If INST/DEL is not being pressed, jump to DK2 below, BNE DK2 ; otherwise let's process the configuration keys .FREEZE ; COPY is being pressed, so we enter a loop that ; listens for configuration keys, and we keep looping ; until we detect a DELETE key press. This effectively ; pauses the game when COPY is pressed, and unpauses ; it when DELETE is pressed JSR WSCAN ; Call WSCAN to wait for the vertical sync, so the whole ; screen gets drawn JSR RDKEY ; Scan the keyboard for a key press and return the ; internal key number in A and X (or 0 for no key press) CPX #$02 ; If "Q" is not being pressed, skip to DK6 BNE DK6 STX DNOIZ ; "Q" is being pressed, so set DNOIZ to a non-zero value ; to turn the sound off .DK6 LDY #0 ; We now want to loop through the keys that toggle ; various settings, so set a counter in Y to work our ; way through them .DKL4 JSR DKS3 ; Call DKS3 to scan for the key given in Y, and toggle ; the relevant setting if it is pressed INY ; Increment Y to point to the next toggle key CPY #(MUFOR-DAMP) ; Check to see whether we have reached the last toggle ; key (i.e. MUFOR-1, as the standard set of options run ; from DAMP to PLTOG, which just before MUFOR) BNE DKL4 ; If not, loop back to check for the next toggle key BIT PATG ; If bit 7 of PATG is clear then the "X" configuration BPL nosillytog ; option has not been enabled, so jump to nosillytog to ; skip the following .DKL42 ; If we get here then the "X" configuration option has ; been enabled, so the title screen shows the authors' ; names, we can force a mis-jump, and we can alter the ; MUFOR, MUDOCK and MUSILLY configuration options, which ; are unavailable by default JSR DKS3 ; Call DKS3 to scan for the key given in Y, and toggle ; the relevant setting if it is pressed INY ; Increment Y to point to the next toggle key CPY #(MUSILLY+1-DAMP) ; Check to see whether we have reached the last toggle ; key (i.e. MUSILLY, as the standard set of options run ; from DAMP to PLTOG, and the extended options run from ; MUFOR to MUSILLY) BNE DKL42 ; If not, loop back to check for the next toggle key .nosillytog LDA MUTOK ; If the value of MUTOK has changed (i.e. it does not CMP MUTOKOLD ; match the value in MUTOKOLD) then the docking music BEQ P%+5 ; has either been enabled or disabled, so call MUTOKCH JSR MUTOKCH ; to process a change in the docking music configuration ; setting CPX #$33 ; If "S" is not being pressed, jump to DK7 BNE DK7 LDA #0 ; "S" is being pressed, so set DNOIZ to 0 to turn the STA DNOIZ ; sound on .DK7 CPX #$07 ; If left arrow is not being pressed, skip over the next BNE P%+5 ; instruction JMP DEATH2 ; Left arrow is being pressed, so jump to DEATH2 to end ; the game CPX #$0D ; If CLR/HOME is not being pressed, we are still paused, BNE FREEZE ; so loop back up to keep listening for configuration ; keys, otherwise fall through into the rest of the ; key detection code, which unpauses the game .DK2 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TT217 ; Type: Subroutine ; Category: Keyboard ; Summary: Scan the keyboard until a key is pressed ; ; ------------------------------------------------------------------------------ ; ; Scan the keyboard until a key is pressed, and return the key's ASCII code. ; If, on entry, a key is already being held down, then wait until that key is ; released first (so this routine detects the first key down event following ; the subroutine call). ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; X The ASCII code of the key that was pressed ; ; A Contains the same as X ; ; Y Y is preserved ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; out Contains an RTS ; ; t As TT217 but don't preserve Y, set it to YSAV instead ; ; ****************************************************************************** .TT217 STY YSAV ; Store Y in temporary storage, so we can restore it ; later .t LDY #2 ; Wait for 2/50 of a second (0.04 seconds) on PAL JSR DELAY ; systems, or 2/60 of a second (0.33 seconds) on NTSC, ; to implement a simple keyboard debounce and prevent ; multiple key presses being recorded JSR RDKEY ; Scan the keyboard for a key press and return the ; internal key number in A and X (or 0 for no key press) BNE t ; If a key was already being held down when we entered ; this routine, keep looping back up to t, until the ; key is released .t2 JSR RDKEY ; Any pre-existing key press is now gone, so we can ; start scanning the keyboard again, returning the ; internal key number in A and X (or 0 for no key press) BEQ t2 ; Keep looping up to t2 until a key is pressed LDA TRANTABLE,X ; TRANTABLE points to the key translation table, which ; is used to translate internal key numbers to ASCII, so ; this fetches the key's ASCII code into A LDY YSAV ; Restore the original value of Y we stored above TAX ; Copy A into X .out RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: me1 ; Type: Subroutine ; Category: Flight ; Summary: Erase an old in-flight message and display a new one ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text token to be printed ; ; X Must be set to 0 ; ; ****************************************************************************** .me1 STX DLY ; Set the message delay in DLY to 0, so any new ; in-flight messages will be shown instantly PHA ; Store the new message token we want to print LDA MCH ; Set A to the token number of the message that is JSR mes9 ; currently on-screen, and call mes9 to print it (which ; will remove it from the screen, as printing is done ; using EOR logic) PLA ; Restore the new message token ; ****************************************************************************** ; ; Name: MESS ; Type: Subroutine ; Category: Flight ; Summary: Display an in-flight message ; ; ------------------------------------------------------------------------------ ; ; Display an in-flight message in capitals at the bottom of the space view, ; erasing any existing in-flight message first. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text token to be printed ; ; ****************************************************************************** .MESS PHA ; Store A on the stack so we can restore it after the ; following LDA #16 ; Set A = 16 to use as the text row for the message if ; this is a space view LDX QQ11 ; If this is the space view, skip the following BEQ infrontvw ; instruction JSR CLYNS ; Clear the bottom three text rows of the upper screen, ; and move the text cursor to the first cleared row LDA #25 ; Set A = 25 to use as the text row for the message if ; this is not a space view EQUB $2C ; Skip the next instruction by turning it into ; $2C $85 $33, or BIT $3385, which does nothing apart ; from affect the flags .infrontvw STA YC ; Move the text cursor to the row specified in A LDX #0 ; Set QQ17 = 0 to switch to ALL CAPS STX QQ17 LDA messXC ; Move the text cursor to column messXC, in case we JSR DOXC ; jump to me1 below to erase the current in-flight ; message (whose column we stored in messXC when we ; called MESS to put it there in the first place) PLA ; Restore A from the stack LDY #20 ; Set Y = 20 for setting the message delay below CPX DLY ; If the message delay in DLY is not zero, jump up to BNE me1 ; me1 to erase the current message first (whose token ; number will be in MCH) STY DLY ; Set the message delay in DLY to 20 STA MCH ; Set MCH to the token we are about to display ; Before we fall through into mes9 to print the token, ; we need to work out the starting column for the ; message we want to print, so it's centred on-screen, ; so the following doesn't print anything, it just uses ; the justified text mechanism to work out the number of ; characters in the message we are going to print LDA #%11000000 ; Set the DTW4 flag to %11000000 (justify text, buffer STA DTW4 ; entire token including carriage returns) LDA de ; Set the C flag to bit 1 of the destruction flag in de LSR A LDA #0 ; Set A = 0 BCC P%+4 ; If the destruction flag in de is not set, skip the ; following instruction LDA #10 ; Set A = 10 STA DTW5 ; Store A in DTW5, so DTW5 (which holds the size of the ; justified text buffer at BUF) is set to 0 if the ; destruction flag is not set, or 10 if it is (10 being ; the number of characters in the " DESTROYED" token) LDA MCH ; Call TT27 to print the token in MCH into the buffer JSR TT27 ; (this doesn't print it on-screen, it just puts it into ; the buffer and moves the DTW5 pointer along, so DTW5 ; now contains the size of the message we want to print, ; including the " DESTROYED" part if that's going to be ; included) LDA #32 ; Set A = (32 - DTW5) / 2 SEC ; SBC DTW5 ; so A now contains the column number we need to print LSR A ; our message at for it to be centred on-screen (as ; there are 32 columns) STA messXC ; Store A in messXC, so when we erase the message via ; the branch to me1 above, messXC will tell us where to ; print it JSR DOXC ; Move the text cursor to column messXC JSR MT15 ; Call MT15 to switch to left-aligned text when printing ; extended tokens disabling the justify text setting we ; set above LDA MCH ; Set MCH to the token we are about to display ; Fall through into mes9 to print the token in A ; ****************************************************************************** ; ; Name: mes9 ; Type: Subroutine ; Category: Flight ; Summary: Print a text token, possibly followed by " DESTROYED" ; ; ------------------------------------------------------------------------------ ; ; Print a text token, followed by " DESTROYED" if the destruction flag is set ; (for when a piece of equipment is destroyed). ; ; ****************************************************************************** .mes9 JSR TT27 ; Call TT27 to print the text token in A LSR de ; If bit 0 of variable de is clear, return from the BCC out ; subroutine (as out contains an RTS) LDA #253 ; Print recursive token 93 (" DESTROYED") and return JMP TT27 ; from the subroutine using a tail call ; ****************************************************************************** ; ; Name: OUCH ; Type: Subroutine ; Category: Flight ; Summary: Potentially lose cargo or equipment following damage ; ; ------------------------------------------------------------------------------ ; ; Our shields are dead and we are taking damage, so there is a small chance of ; losing cargo or equipment. ; ; ****************************************************************************** .OUCH JSR DORND ; Set A and X to random numbers BMI out ; If A < 0 (50% chance), return from the subroutine ; (as out contains an RTS) CPX #22 ; If X >= 22 (91% chance), return from the subroutine BCS out ; (as out contains an RTS) LDA QQ20,X ; If we do not have any of item QQ20+X, return from the BEQ out ; subroutine (as out contains an RTS). X is in the range ; 0-21, so this not only checks for cargo, but also for ; E.C.M., fuel scoops, energy bomb, energy unit and ; docking computer, all of which can be destroyed LDA DLY ; If there is already an in-flight message on-screen, BNE out ; return from the subroutine (as out contains an RTS) LDY #3 ; Set bit 1 of de, the equipment destruction flag, so STY de ; that when we call MESS below, " DESTROYED" is appended ; to the in-flight message STA QQ20,X ; A is 0 (as we didn't branch with the BNE above), so ; this sets QQ20+X to 0, which destroys any cargo or ; equipment we have of that type CPX #17 ; If X >= 17 then we just lost a piece of equipment, so BCS ou1 ; jump to ou1 to print the relevant message TXA ; Print recursive token 48 + A as an in-flight token, ADC #208 ; which will be in the range 48 ("FOOD") to 64 ("ALIEN JMP MESS ; ITEMS") as the C flag is clear, so this prints the ; destroyed item's name, followed by " DESTROYED" (as we ; set bit 1 of the de flag above), and returns from the ; subroutine using a tail call .ou1 BEQ ou2 ; If X = 17, jump to ou2 to print "E.C.M.SYSTEM ; DESTROYED" and return from the subroutine using a tail ; call CPX #18 ; If X = 18, jump to ou3 to print "FUEL SCOOPS BEQ ou3 ; DESTROYED" and return from the subroutine using a tail ; call TXA ; Otherwise X is in the range 19 to 21 and the C flag is ADC #113-20 ; set (as we got here via a BCS to ou1), so we set A as ; follows: ; ; A = 113 - 20 + X + C ; = 113 - 19 + X ; = 113 to 115 JMP MESS ; Print recursive token A ("ENERGY BOMB", "ENERGY UNIT" ; or "DOCKING COMPUTERS") as an in-flight message, ; followed by " DESTROYED", and return from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: ou2 ; Type: Subroutine ; Category: Flight ; Summary: Display "E.C.M.SYSTEM DESTROYED" as an in-flight message ; ; ****************************************************************************** .ou2 LDA #108 ; Set A to recursive token 108 ("E.C.M.SYSTEM") JMP MESS ; Print recursive token A as an in-flight message, ; followed by " DESTROYED", and return from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: ou3 ; Type: Subroutine ; Category: Flight ; Summary: Display "FUEL SCOOPS DESTROYED" as an in-flight message ; ; ****************************************************************************** .ou3 LDA #111 ; Set A to recursive token 111 ("FUEL SCOOPS") JMP MESS ; Print recursive token A as an in-flight message, ; followed by " DESTROYED", and return from the ; subroutine using a tail call ; ****************************************************************************** ; ; Name: ITEM ; Type: Macro ; Category: Market ; Summary: Macro definition for the market prices table ; Deep dive: Market item prices and availability ; ; ------------------------------------------------------------------------------ ; ; The following macro is used to build the market prices table: ; ; ITEM price, factor, units, quantity, mask ; ; It inserts an item into the market prices table at QQ23. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; price Base price ; ; factor Economic factor ; ; units Units: "t", "g" or "k" ; ; quantity Base quantity ; ; mask Fluctuations mask ; ; ****************************************************************************** MACRO ITEM price, factor, units, quantity, mask IF factor < 0 s = 1 << 7 ELSE s = 0 ENDIF IF units = 't' u = 0 ELIF units = 'k' u = 1 << 5 ELSE u = 1 << 6 ENDIF e = ABS(factor) EQUB price EQUB s + u + e EQUB quantity EQUB mask ENDMACRO ; ****************************************************************************** ; ; Name: QQ23 ; Type: Variable ; Category: Market ; Summary: Market prices table ; Deep dive: Market item prices and availability ; ; ------------------------------------------------------------------------------ ; ; Each item has four bytes of data, like this: ; ; Byte #0 = Base price ; Byte #1 = Economic factor in bits 0-4, with the sign in bit 7 ; Unit in bits 5-6 ; Byte #2 = Base quantity ; Byte #3 = Mask to control price fluctuations ; ; To make it easier for humans to follow, I've defined a macro called ITEM ; that takes the following arguments and builds the four bytes for us: ; ; ITEM base price, economic factor, units, base quantity, mask ; ; So for food, we have the following, for example: ; ; * Base price = 19 ; * Economic factor = -2 ; * Unit = tonnes ; * Base quantity = 6 ; * Mask = %00000001 ; ; ****************************************************************************** .QQ23 ITEM 19, -2, 't', 6, %00000001 ; 0 = Food ITEM 20, -1, 't', 10, %00000011 ; 1 = Textiles ITEM 65, -3, 't', 2, %00000111 ; 2 = Radioactives ITEM 40, -5, 't', 226, %00011111 ; 3 = Slaves ITEM 83, -5, 't', 251, %00001111 ; 4 = Liquor/Wines ITEM 196, 8, 't', 54, %00000011 ; 5 = Luxuries ITEM 235, 29, 't', 8, %01111000 ; 6 = Narcotics ITEM 154, 14, 't', 56, %00000011 ; 7 = Computers ITEM 117, 6, 't', 40, %00000111 ; 8 = Machinery ITEM 78, 1, 't', 17, %00011111 ; 9 = Alloys ITEM 124, 13, 't', 29, %00000111 ; 10 = Firearms ITEM 176, -9, 't', 220, %00111111 ; 11 = Furs ITEM 32, -1, 't', 53, %00000011 ; 12 = Minerals ITEM 97, -1, 'k', 66, %00000111 ; 13 = Gold ;EQUD $360A118 ; This data is commented out in the original source ; ; It would have inserted an item as follows: ; ; ITEM 24, -1, 'k', 96, %00000011 ; ; So that's an item with a base price of 24 credits that ; is slightly cheaper than average in agricultural ; economies but closer to average in rich industrial ; ones, with a base quantity of 96kg and a reasonably ; stable price ; ; I wonder what this mysterious item was going to be? ITEM 171, -2, 'k', 55, %00011111 ; 14 = Platinum ITEM 45, -1, 'g', 250, %00001111 ; 15 = Gem-Stones ITEM 53, 15, 't', 192, %00000111 ; 16 = Alien items ; ****************************************************************************** ; ; Name: TIDY ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Orthonormalise the orientation vectors for a ship ; Deep dive: Tidying orthonormal vectors ; Orientation vectors ; ; ------------------------------------------------------------------------------ ; ; This routine orthonormalises the orientation vectors for a ship. This means ; making the three orientation vectors orthogonal (perpendicular to each other), ; and normal (so each of the vectors has length 1). ; ; We do this because we use the small angle approximation to rotate these ; vectors in space. It is not completely accurate, so the three vectors tend ; to get stretched over time, so periodically we tidy the vectors with this ; routine to ensure they remain as orthonormal as possible. ; ; ****************************************************************************** .TI2 ; Called from below with A = 0, X = 0, Y = 4 when ; nosev_x and nosev_y are small, so we assume that ; nosev_z is big TYA ; A = Y = 4 LDY #2 JSR TIS3 ; Call TIS3 with X = 0, Y = 2, A = 4, to set roofv_z = STA INWK+20 ; -(nosev_x * roofv_x + nosev_y * roofv_y) / nosev_z JMP TI3 ; Jump to TI3 to keep tidying .TI1 ; Called from below with A = 0, Y = 4 when nosev_x is ; small TAX ; Set X = A = 0 LDA XX15+1 ; Set A = nosev_y, and if the top two magnitude bits AND #%01100000 ; are both clear, jump to TI2 with A = 0, X = 0, Y = 4 BEQ TI2 LDA #2 ; Otherwise nosev_y is big, so set up the index values ; to pass to TIS3 JSR TIS3 ; Call TIS3 with X = 0, Y = 4, A = 2, to set roofv_y = STA INWK+18 ; -(nosev_x * roofv_x + nosev_z * roofv_z) / nosev_y JMP TI3 ; Jump to TI3 to keep tidying .TIDY LDA INWK+10 ; Set (XX15, XX15+1, XX15+2) = nosev STA XX15 LDA INWK+12 STA XX15+1 LDA INWK+14 STA XX15+2 JSR NORM ; Call NORM to normalise the vector in XX15, i.e. nosev LDA XX15 ; Set nosev = (XX15, XX15+1, XX15+2) STA INWK+10 LDA XX15+1 STA INWK+12 LDA XX15+2 STA INWK+14 LDY #4 ; Set Y = 4 LDA XX15 ; Set A = nosev_x, and if the top two magnitude bits AND #%01100000 ; are both clear, jump to TI1 with A = 0, Y = 4 BEQ TI1 LDX #2 ; Otherwise nosev_x is big, so set up the index values LDA #0 ; to pass to TIS3 JSR TIS3 ; Call TIS3 with X = 2, Y = 4, A = 0, to set roofv_x = STA INWK+16 ; -(nosev_y * roofv_y + nosev_z * roofv_z) / nosev_x .TI3 LDA INWK+16 ; Set (XX15, XX15+1, XX15+2) = roofv STA XX15 LDA INWK+18 STA XX15+1 LDA INWK+20 STA XX15+2 JSR NORM ; Call NORM to normalise the vector in XX15, i.e. roofv LDA XX15 ; Set roofv = (XX15, XX15+1, XX15+2) STA INWK+16 LDA XX15+1 STA INWK+18 LDA XX15+2 STA INWK+20 LDA INWK+12 ; Set Q = nosev_y STA Q LDA INWK+20 ; Set A = roofv_z JSR MULT12 ; Set (S R) = Q * A = nosev_y * roofv_z LDX INWK+14 ; Set X = nosev_z LDA INWK+18 ; Set A = roofv_y JSR TIS1 ; Set (A ?) = (-X * A + (S R)) / 96 ; = (-nosev_z * roofv_y + nosev_y * roofv_z) / 96 ; ; This also sets Q = nosev_z EOR #%10000000 ; Set sidev_x = -A STA INWK+22 ; = (nosev_z * roofv_y - nosev_y * roofv_z) / 96 LDA INWK+16 ; Set A = roofv_x JSR MULT12 ; Set (S R) = Q * A = nosev_z * roofv_x LDX INWK+10 ; Set X = nosev_x LDA INWK+20 ; Set A = roofv_z JSR TIS1 ; Set (A ?) = (-X * A + (S R)) / 96 ; = (-nosev_x * roofv_z + nosev_z * roofv_x) / 96 ; ; This also sets Q = nosev_x EOR #%10000000 ; Set sidev_y = -A STA INWK+24 ; = (nosev_x * roofv_z - nosev_z * roofv_x) / 96 LDA INWK+18 ; Set A = roofv_y JSR MULT12 ; Set (S R) = Q * A = nosev_x * roofv_y LDX INWK+12 ; Set X = nosev_y LDA INWK+16 ; Set A = roofv_x JSR TIS1 ; Set (A ?) = (-X * A + (S R)) / 96 ; = (-nosev_y * roofv_x + nosev_x * roofv_y) / 96 EOR #%10000000 ; Set sidev_z = -A STA INWK+26 ; = (nosev_y * roofv_x - nosev_x * roofv_y) / 96 LDA #0 ; Set A = 0 so we can clear the low bytes of the ; orientation vectors LDX #14 ; We want to clear the low bytes, so start from sidev_y ; at byte #9+14 (we clear all except sidev_z_lo, though ; I suspect this is in error and that X should be 16) .TIL1 STA INWK+9,X ; Set the low byte in byte #9+X to zero DEX ; Set X = X - 2 to jump down to the next low byte DEX BPL TIL1 ; Loop back until we have zeroed all the low bytes RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TIS2 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate A = A / Q ; Deep dive: Shift-and-subtract division ; ; ------------------------------------------------------------------------------ ; ; Calculate the following division, where A is a sign-magnitude number and Q is ; a positive integer: ; ; A = A / Q ; ; The value of A is returned as a sign-magnitude number with 96 representing 1, ; and the maximum value returned is 1 (i.e. 96). This routine is used when ; normalising vectors, where we represent fractions using integers, so this ; gives us an approximation to two decimal places. ; ; ****************************************************************************** .TIS2 TAY ; Store the argument A in Y AND #%01111111 ; Strip the sign bit from the argument, so A = |A| CMP Q ; If A >= Q then jump to TI4 to return a 1 with the BCS TI4 ; correct sign LDX #%11111110 ; Set T to have bits 1-7 set, so we can rotate through 7 STX T ; loop iterations, getting a 1 each time, and then ; getting a 0 on the 8th iteration... and we can also ; use T to catch our result bits into bit 0 each time .TIL2 ASL A ; Shift A to the left CMP Q ; If A < Q skip the following subtraction BCC P%+4 SBC Q ; A >= Q, so set A = A - Q ; ; Going into this subtraction we know the C flag is ; set as we passed through the BCC above, and we also ; know that A >= Q, so the C flag will still be set once ; we are done ROL T ; Rotate the counter in T to the left, and catch the ; result bit into bit 0 (which will be a 0 if we didn't ; do the subtraction, or 1 if we did) BCS TIL2 ; If we still have set bits in T, loop back to TIL2 to ; do the next iteration of 7 ; We've done the division and now have a result in the ; range 0-255 here, which we need to reduce to the range ; 0-96. We can do that by multiplying the result by 3/8, ; as 256 * 3/8 = 96 LDA T ; Set T = T / 4 LSR A LSR A STA T LSR A ; Set T = T / 8 + T / 4 ADC T ; = 3T / 8 STA T TYA ; Fetch the sign bit of the original argument A AND #%10000000 ORA T ; Apply the sign bit to T RTS ; Return from the subroutine .TI4 TYA ; Fetch the sign bit of the original argument A AND #%10000000 ORA #96 ; Apply the sign bit to 96 (which represents 1) RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TIS3 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate -(nosev_1 * roofv_1 + nosev_2 * roofv_2) / nosev_3 ; ; ------------------------------------------------------------------------------ ; ; Calculate the following expression: ; ; A = -(nosev_1 * roofv_1 + nosev_2 * roofv_2) / nosev_3 ; ; where 1, 2 and 3 are x, y, or z, depending on the values of X, Y and A. This ; routine is called with the following values: ; ; X = 0, Y = 2, A = 4 -> ; A = -(nosev_x * roofv_x + nosev_y * roofv_y) / nosev_z ; ; X = 0, Y = 4, A = 2 -> ; A = -(nosev_x * roofv_x + nosev_z * roofv_z) / nosev_y ; ; X = 2, Y = 4, A = 0 -> ; A = -(nosev_y * roofv_y + nosev_z * roofv_z) / nosev_x ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X Index 1 (0 = x, 2 = y, 4 = z) ; ; Y Index 2 (0 = x, 2 = y, 4 = z) ; ; A Index 3 (0 = x, 2 = y, 4 = z) ; ; ****************************************************************************** .TIS3 STA P+2 ; Store P+2 in A for later LDA INWK+10,X ; Set Q = nosev_x_hi (plus X) STA Q LDA INWK+16,X ; Set A = roofv_x_hi (plus X) JSR MULT12 ; Set (S R) = Q * A ; = nosev_x_hi * roofv_x_hi LDX INWK+10,Y ; Set Q = nosev_x_hi (plus Y) STX Q LDA INWK+16,Y ; Set A = roofv_x_hi (plus Y) JSR MAD ; Set (A X) = Q * A + (S R) ; = (nosev_x,X * roofv_x,X) + ; (nosev_x,Y * roofv_x,Y) STX P ; Store low byte of result in P, so result is now in ; (A P) LDY P+2 ; Set Q = roofv_x_hi (plus argument A) LDX INWK+10,Y STX Q EOR #%10000000 ; Flip the sign of A ; Fall through into DIVDT to do: ; ; (P+1 A) = (A P) / Q ; ; = -((nosev_x,X * roofv_x,X) + ; (nosev_x,Y * roofv_x,Y)) ; / nosev_x,A ; ****************************************************************************** ; ; Name: DVIDT ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (P+1 A) = (A P) / Q ; ; ------------------------------------------------------------------------------ ; ; Calculate the following integer division between sign-magnitude numbers: ; ; (P+1 A) = (A P) / Q ; ; This uses the same shift-and-subtract algorithm as TIS2. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; itsoff Contains an RTS ; ; ****************************************************************************** .DVIDT STA P+1 ; Set P+1 = A, so P(1 0) = (A P) EOR Q ; Set T = the sign bit of A EOR Q, so it's 1 if A and Q AND #%10000000 ; have different signs, i.e. it's the sign of the result STA T ; of A / Q LDA #0 ; Set A = 0 for us to build a result LDX #16 ; Set a counter in X to count the 16 bits in P(1 0) ASL P ; Shift P(1 0) left ROL P+1 ASL Q ; Clear the sign bit of Q the C flag at the same time LSR Q .DVL2 ROL A ; Shift A to the left CMP Q ; If A < Q skip the following subtraction BCC P%+4 SBC Q ; Set A = A - Q ; ; Going into this subtraction we know the C flag is ; set as we passed through the BCC above, and we also ; know that A >= Q, so the C flag will still be set once ; we are done ROL P ; Rotate P(1 0) to the left, and catch the result bit ROL P+1 ; into the C flag (which will be a 0 if we didn't ; do the subtraction, or 1 if we did) DEX ; Decrement the loop counter BNE DVL2 ; Loop back for the next bit until we have done all 16 ; bits of P(1 0) LDA P ; Set A = P so the low byte is in the result in A ORA T ; Set A to the correct sign bit that we set in T above .itsoff RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: startat ; Type: Subroutine ; Category: Sound ; Summary: Start playing the title music, if configured ; ; ****************************************************************************** IF _GMA_RELEASE .startat LDA #LO(THEME-1) ; Set (A X) to THEME-1, which is the address before LDX #HI(THEME-1) ; the start of the title music at THEME BNE startat2 ; Jump to startat2 to play the title music (this BNE is ; effectively a JMP as X is never zero) ENDIF ; ****************************************************************************** ; ; Name: startbd ; Type: Subroutine ; Category: Sound ; Summary: Start playing the docking music, if configured ; Deep dive: Music in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; april16 Start playing the docking music, irrespective of the ; current configuration settings ; ; startat2 Start playing the music at address (A X) + 1 ; ; ****************************************************************************** .startbd IF _GMA_RELEASE BIT MUDOCK ; If bit 7 of MUDOCK is set then the docking computer BMI startat ; has been configured to play the title music rather ; than the docking music, so jump to startat to set ; (A X) to the address of the title music to play when ; docking LDA #LO(musicstart) ; Set (A X) = musicstart, the address before the start LDX #HI(musicstart) ; of the docking music .startat2 STA value5 ; Set value5(1 0) = (A X) STX value5+1 ; ; So value5 contains the address before the start of the ; music we want to play ENDIF BIT MUPLA ; If bit 7 of MUPLA is set then there is already music BMI itsoff ; playing so we don't want to start any more, so jump to ; itsoff to return from the subroutine (as itsoff ; contains an RTS) BIT MUFOR ; If bit 7 of MUFOR is set then the docking music is BMI april16 ; configured so that it cannot be disabled, so skip the ; following check for MUTOK BIT MUTOK ; If bit 7 of MUTOK is set then the docking music is BMI itsoff ; disabled, so jump to itsoff to return from the ; subroutine without playing the docking music (as ; itsoff contains an RTS) .april16 LDA #%101 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 1 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on ; ; See the memory map at the top of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore JSR BDENTRY ; Start playing the configured background music LDA #$FF ; Set MUPLA to $FF to indicate that music is now playing STA MUPLA BNE coffeeex ; Jump to coffeeex to restore the memory configuration ; and return from the subroutine (this BNE is ; effectively a JMP as A is never zero) ; ****************************************************************************** ; ; Name: MUTOKCH ; Type: Subroutine ; Category: Sound ; Summary: Process a change in the docking music configuration setting ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The new value of MUTOK ; ; ****************************************************************************** .MUTOKCH STA MUTOKOLD ; Store the new value of MUTOK in MUTOKOLD so we can ; check whether it changes again EOR #$FF ; If MUTOK = 0 and bit 7 of auto is set, then the AND auto ; docking music has just been enabled and the docking BMI april16 ; computer is running, so jump to april16 to start ; playing the docking music ; Otherwise either the docking music has just been ; disabled and/or the docking computer is not running, ; so fall through into stopbd to stop playing the ; docking music ; ****************************************************************************** ; ; Name: stopbd ; Type: Subroutine ; Category: Sound ; Summary: Stop playing the docking music ; Deep dive: Music in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; stopat Stop playing the current music ; ; coffeeex Restore the memory configuration and return from the ; subroutine ; ; ****************************************************************************** .stopbd IF _GMA_RELEASE BIT MULIE ; If bit 7 of MULIE is set then we got here via the call BMI itsoff ; to the RESET routine from the TITLE routine ; ; When this is the case, we do not want to stop the ; title music from playing, so jump to itsoff to return ; from the subroutine without doing anything (as itsoff ; contains an RTS) ENDIF BIT MUFOR ; If bit 7 of MUFOR is set then the docking music is BMI startbd ; configured so that it cannot be disabled, so jump to ; startbd to start playing the docking music instead .stopat BIT MUPLA ; If bit 7 of MUPLA is clear then no music is currently BPL itsoff ; playing, so jump to itsoff to return from the ; subroutine (as itsoff contains an RTS) JSR SOFLUSH ; Call SOFLUSH to reset the sound buffers LDA #%101 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 1 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on ; ; See the memory map at the top of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore LDA #0 ; Set MUPLA to 0 to indicate that no music is playing STA MUPLA LDX #$18 ; We now want to zero SID registers $00 to $18, so set ; an index counter in X SEI ; Disable interrupts while we update the SID registers .coffeeloop STA SID,X ; Zero the X-th SID register DEX ; Decrement the loop counter BPL coffeeloop ; Loop back until we have zeroed all SID registers from ; $18 down to $00 LDA #%00001111 ; Set SID register $18 to control the sound as follows: STA SID+$18 ; ; * Bits 0-3: set the volume to 15 (maximum) ; ; * Bit 4 clear: disable the low-pass filter ; ; * Bit 5 clear: disable the bandpass filter ; ; * Bit 6 clear: disable the high-pass filter ; ; * Bit 7 clear: enable voice 3 CLI ; Enable interrupts again .coffeeex LDA #%100 ; Call SETL1 to set the 6510 input/output port to the JMP SETL1 ; following: ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; and return from the subroutine using a tail call ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore ; ****************************************************************************** ; ; Name: KTRAN ; Type: Variable ; Category: Keyboard ; Summary: An unused key logger buffer that's left over from the 6502 Second ; Processor version of Elite ; ; ****************************************************************************** .buf EQUB 2 ; Transmit 2 bytes as part of this command EQUB 15 ; Receive 15 bytes as part of this command .KTRAN EQUS "1234567890" ; A 17-byte buffer to hold the key logger data from the EQUS "1234567" ; KEYBOARD routine in the I/O processor (note that only ; 12 of these bytes are actually updated by the KEYBOARD ; routine) ; ****************************************************************************** ; ; Name: TRANTABLE ; Type: Variable ; Category: Keyboard ; Summary: Translation table from internal key number to ASCII ; ; ------------------------------------------------------------------------------ ; ; This table translates internal key numbers (i.e. the offset of a key in the ; key logger table at KEYLOOK) into ASCII. ; ; ****************************************************************************** .TRANTABLE EQUB 0 ; Not used EQUB 1 ; RUN/STOP is translated to ASCII 1 EQUS "Q" EQUB 2 ; "C=" is translated to ASCII 2 EQUS " 2" EQUB 3 ; CTRL is translated to ASCII 3 EQUB 27 ; Left arrow is translated to ASCII 27 EQUS "1/" EQUS "^" ; UP arrow is translated to "^" EQUS "=" EQUB 5 ; Right SHIFT is translated to ASCII 5 EQUB 6 ; CLR/HOME is translated to ASCII 6 EQUS ";*" EQUS "`" ; "£" is translated to "`" EQUS ",@:.-LP+" EQUS "NOKM0JI9" EQUS "VUHB8GY7" EQUS "XTFC6DR5" EQUB 7 ; Left SHIFT is translated to ASCII 7 EQUS "ESZ4AW3" EQUB 8 ; Cursor up/down is translated to ASCII 8 EQUB 9 ; F5 is translated to ASCII 9 EQUB 10 ; F3 is translated to ASCII 10 EQUB 11 ; F1 is translated to ASCII 11 EQUB 12 ; F7 is translated to ASCII 12 EQUB 14 ; Cursor left/right is translated to ASCII 14 EQUB 13 ; RETURN is translated to ASCII 13 EQUB 127 ; INS/DEL is translated to ASCII 127 ; ****************************************************************************** ; ; Save ELTF.bin ; ; ****************************************************************************** PRINT "ELITE F" PRINT "Assembled at ", ~CODE_F% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_F%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_F% PRINT "S.ELTF ", ~CODE_F%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_F% SAVE "3-assembled-output/ELTF.bin", CODE_F%, P%, LOAD% ; ****************************************************************************** ; ; ELITE G FILE ; ; Produces the binary file ELTG.bin that gets loaded by elite-checksum.py. ; ; ****************************************************************************** CODE_G% = P% LOAD_G% = LOAD% + P% - CODE% IF _GMA_RELEASE EQUB $A9, $05, $20, $7F, $82, $A9, $00, $8D ; These bytes appear to be EQUB $15, $D0, $A9, $04, $78, $8D, $8E, $82 ; unused and just contain random EQUB $A5, $01, $29, $F8, $0D, $8E, $82, $85 ; workspace noise left over from EQUB $01, $58, $60, $04, $A5, $2E, $8D, $F2 ; the BBC Micro assembly process EQUB $04, $A5, $2F, $8D, $F3, $04, $60, $A6 EQUB $9D, $20, $F3, $82, $A6, $9D, $4C, $2F EQUB $20, $20, $47, $84, $20, $4F, $7B, $8D EQUB $53, $04, $8D, $5F, $04, $20, $0E, $B1 EQUB $A9 ELIF _SOURCE_DISK EQUB $A2, $36, $B5, $00, $BC, $00, $CE, $9D ; These bytes appear to be EQUB $00, $CE, $94, $00, $E8, $D0, $F3, $60 ; unused and just contain random EQUB $A9, $05, $20, $7F, $8B, $A9, $00, $8D ; workspace noise left over from EQUB $15, $D0, $A9, $04, $78, $8D, $8E, $8B ; the BBC Micro assembly process EQUB $A5, $01, $29, $F8, $0D, $8E, $8B, $85 ; EQUB $01, $58, $60, $04, $A5, $2E, $8D, $F2 ; They contain parts of the EQUB $04, $A5, $2F, $8D, $F3, $04, $60, $A6 ; SWAPPZERO, NOSPRITES and KS3 EQUB $9D, $20, $F3, $8B, $A6, $9D, $4C, $2C ; routines from when they were EQUB $20, $20, $47, $8D, $20, $3F, $84, $8D ; assembled in memory EQUB $53, $04, $8D, $5F, $04, $20, $0E, $BA EQUB $A9, $06, $85, $0E, $A9, $81, $4C, $5B EQUB $85, $A2, $FF, $E8, $BD, $52, $04, $F0 EQUB $CB, $C9, $01, $D0, $F6, $8A, $0A, $A8 EQUB $B9, $A1, $28, $85, $07, $B9, $A2 ENDIF ; ****************************************************************************** ; ; Name: log ; Type: Variable ; Category: Maths (Arithmetic) ; Summary: Binary logarithm table (high byte) ; ; ------------------------------------------------------------------------------ ; ; At byte n, the table contains the high byte of: ; ; $2000 * log10(n) / log10(2) = 32 * 256 * log10(n) / log10(2) ; ; where log10 is the logarithm to base 10. The change-of-base formula says that: ; ; log2(n) = log10(n) / log10(2) ; ; so byte n contains the high byte of: ; ; 32 * log2(n) * 256 ; ; ****************************************************************************** .log IF _MATCH_ORIGINAL_BINARIES IF _GMA_RELEASE EQUB $06 ; This byte appears to be unused and just contains ; random workspace noise left over from the BBC Micro ; assembly process ELIF _SOURCE_DISK EQUB $28 ; This byte appears to be unused and just contains ; random workspace noise left over from the BBC Micro ; assembly process ENDIF EQUB $00, $20, $32, $40, $4A, $52, $59 EQUB $5F, $65, $6A, $6E, $72, $76, $79, $7D EQUB $80, $82, $85, $87, $8A, $8C, $8E, $90 EQUB $92, $94, $96, $98, $99, $9B, $9D, $9E EQUB $A0, $A1, $A2, $A4, $A5, $A6, $A7, $A9 EQUB $AA, $AB, $AC, $AD, $AE, $AF, $B0, $B1 EQUB $B2, $B3, $B4, $B5, $B6, $B7, $B8, $B9 EQUB $B9, $BA, $BB, $BC, $BD, $BD, $BE, $BF EQUB $BF, $C0, $C1, $C2, $C2, $C3, $C4, $C4 EQUB $C5, $C6, $C6, $C7, $C7, $C8, $C9, $C9 EQUB $CA, $CA, $CB, $CC, $CC, $CD, $CD, $CE EQUB $CE, $CF, $CF, $D0, $D0, $D1, $D1, $D2 EQUB $D2, $D3, $D3, $D4, $D4, $D5, $D5, $D5 EQUB $D6, $D6, $D7, $D7, $D8, $D8, $D9, $D9 EQUB $D9, $DA, $DA, $DB, $DB, $DB, $DC, $DC EQUB $DD, $DD, $DD, $DE, $DE, $DE, $DF, $DF EQUB $E0, $E0, $E0, $E1, $E1, $E1, $E2, $E2 EQUB $E2, $E3, $E3, $E3, $E4, $E4, $E4, $E5 EQUB $E5, $E5, $E6, $E6, $E6, $E7, $E7, $E7 EQUB $E7, $E8, $E8, $E8, $E9, $E9, $E9, $EA EQUB $EA, $EA, $EA, $EB, $EB, $EB, $EC, $EC EQUB $EC, $EC, $ED, $ED, $ED, $ED, $EE, $EE EQUB $EE, $EE, $EF, $EF, $EF, $EF, $F0, $F0 EQUB $F0, $F1, $F1, $F1, $F1, $F1, $F2, $F2 EQUB $F2, $F2, $F3, $F3, $F3, $F3, $F4, $F4 EQUB $F4, $F4, $F5, $F5, $F5, $F5, $F5, $F6 EQUB $F6, $F6, $F6, $F7, $F7, $F7, $F7, $F7 EQUB $F8, $F8, $F8, $F8, $F9, $F9, $F9, $F9 EQUB $F9, $FA, $FA, $FA, $FA, $FA, $FB, $FB EQUB $FB, $FB, $FB, $FC, $FC, $FC, $FC, $FC EQUB $FD, $FD, $FD, $FD, $FD, $FD, $FE, $FE EQUB $FE, $FE, $FE, $FF, $FF, $FF, $FF, $FF ELSE SKIP 1 FOR I%, 1, 255 EQUB HI(INT($2000 * LOG(I%) / LOG(2) + 0.5)) NEXT ENDIF ; ****************************************************************************** ; ; Name: logL ; Type: Variable ; Category: Maths (Arithmetic) ; Summary: Binary logarithm table (low byte) ; ; ------------------------------------------------------------------------------ ; ; Byte n contains the low byte of: ; ; 32 * log2(n) * 256 ; ; ****************************************************************************** .logL IF _MATCH_ORIGINAL_BINARIES IF _GMA_RELEASE EQUB $AE ; This byte appears to be unused and just contains ; random workspace noise left over from the BBC Micro ; assembly process ELIF _SOURCE_DISK EQUB $10 ; This byte appears to be unused and just contains ; random workspace noise left over from the BBC Micro ; assembly process ENDIF EQUB $00, $00, $B8, $00, $4D, $B8, $D5 EQUB $FF, $70, $4D, $B3, $B8, $6A, $D5, $05 EQUB $00, $CC, $70, $EF, $4D, $8D, $B3, $C1 EQUB $B8, $9A, $6A, $28, $D5, $74, $05, $88 EQUB $00, $6B, $CC, $23, $70, $B3, $EF, $22 EQUB $4D, $71, $8D, $A3, $B3, $BD, $C1, $BF EQUB $B8, $AB, $9A, $84, $6A, $4B, $28, $00 EQUB $D5, $A7, $74, $3E, $05, $C8, $88, $45 EQUB $FF, $B7, $6B, $1D, $CC, $79, $23, $CA EQUB $70, $13, $B3, $52, $EF, $89, $22, $B8 EQUB $4D, $E0, $71, $00, $8D, $19, $A3, $2C EQUB $B3, $39, $BD, $3F, $C1, $40, $BF, $3C EQUB $B8, $32, $AB, $23, $9A, $10, $84, $F7 EQUB $6A, $DB, $4B, $BA, $28, $94, $00, $6B EQUB $D5, $3E, $A7, $0E, $74, $DA, $3E, $A2 EQUB $05, $67, $C8, $29, $88, $E7, $45, $A3 EQUB $00, $5B, $B7, $11, $6B, $C4, $1D, $75 EQUB $CC, $23, $79, $CE, $23, $77, $CA, $1D EQUB $70, $C1, $13, $63, $B3, $03, $52, $A1 EQUB $EF, $3C, $89, $D6, $22, $6D, $B8, $03 EQUB $4D, $96, $E0, $28, $71, $B8, $00, $47 EQUB $8D, $D4, $19, $5F, $A3, $E8, $2C, $70 EQUB $B3, $F6, $39, $7B, $BD, $FE, $3F, $80 EQUB $C1, $01, $40, $80, $BF, $FD, $3C, $7A EQUB $B8, $F5, $32, $6F, $AB, $E7, $23, $5F EQUB $9A, $D5, $10, $4A, $84, $BE, $F7, $31 EQUB $6A, $A2, $DB, $13, $4B, $82, $BA, $F1 EQUB $28, $5E, $94, $CB, $00, $36, $6B, $A0 EQUB $D5, $0A, $3E, $73, $A7, $DA, $0E, $41 EQUB $74, $A7, $DA, $0C, $3E, $70, $A2, $D3 EQUB $05, $36, $67, $98, $C8, $F8, $29, $59 EQUB $88, $B8, $E7, $16, $45, $74, $A3, $D1 ELSE SKIP 1 FOR I%, 1, 255 EQUB LO(INT($2000 * LOG(I%) / LOG(2) + 0.5)) NEXT ENDIF ; ****************************************************************************** ; ; Name: antilog ; Type: Variable ; Category: Maths (Arithmetic) ; Summary: Binary antilogarithm table ; ; ------------------------------------------------------------------------------ ; ; At byte n, the table contains: ; ; 2^((n / 2 + 128) / 16) / 256 ; ; which equals: ; ; 2^(n / 32 + 8) / 256 ; ; ****************************************************************************** .antilog FOR I%, 0, 255 EQUB HI(INT(2^((I% / 2 + 128) / 16) + 0.5)) NEXT ; ****************************************************************************** ; ; Name: antilogODD ; Type: Variable ; Category: Maths (Arithmetic) ; Summary: Binary antilogarithm table ; ; ------------------------------------------------------------------------------ ; ; At byte n, the table contains: ; ; 2^((n / 2 + 128.25) / 16) / 256 ; ; which equals: ; ; 2^(n / 32 + 8.015625) / 256 = 2^(n / 32 + 8) * 2^(.015625) / 256 ; = (2^(n / 32 + 8) + 1) / 256 ; ; ****************************************************************************** .antilogODD FOR I%, 0, 255 EQUB HI(INT(2^((I% / 2 + 128.25) / 16) + 0.5)) NEXT ; ****************************************************************************** ; ; Name: ylookupl ; Type: Variable ; Category: Drawing pixels ; Summary: Lookup table for converting a pixel y-coordinate to the low byte ; of a screen address (within the 256-pixel wide game screen) ; Deep dive: Drawing pixels in the Commodore 64 version ; ; ------------------------------------------------------------------------------ ; ; The address returned is indented by four character blocks from the edge of the ; screen (that's the $20 part, as each character is 8 bytes, and 4 * 8 = $20). ; ; This is because the first four characters of every character line are blank, ; so the 256-pixel wide game screen is centred in the Commodore 64's screen ; width of 320 pixels. ; ; ****************************************************************************** .ylookupl FOR I%, 0, 255 EQUB LO(SCBASE + $20 + ((I% AND $F8) * 40)) NEXT ; ****************************************************************************** ; ; Name: ylookuph ; Type: Variable ; Category: Drawing pixels ; Summary: Lookup table for converting a pixel y-coordinate to the high byte ; of a screen address (within the 256-pixel wide game screen) ; Deep dive: Drawing pixels in the Commodore 64 version ; ; ------------------------------------------------------------------------------ ; ; The address returned is indented by four character blocks from the edge of the ; screen (that's the $20 part, as each character is 8 bytes, and 4 * 8 = $20). ; ; This is because the first four characters of every character line are blank, ; so the 256-pixel wide game screen is centred in the Commodore 64's screen ; width of 320 pixels. ; ; ****************************************************************************** .ylookuph FOR I%, 0, 255 EQUB HI(SCBASE + $20 + ((I% AND $F8) * 40)) NEXT ; ****************************************************************************** ; ; Name: celllookl ; Type: Variable ; Category: Drawing pixels ; Summary: Lookup table for converting a text y-coordinate to the low byte ; of the address of the start of the character row ; ; ------------------------------------------------------------------------------ ; ; In the text view, screen RAM is used to determine the colour of each on-screen ; character, so this table is used to look up the address of the colour ; information for the start of a specific text row. ; ; ****************************************************************************** .celllookl FOR I%, 0, 24 EQUB LO(SCBASE + $2003 + (40 * I%)) NEXT ; ****************************************************************************** ; ; Name: celllookh ; Type: Variable ; Category: Drawing pixels ; Summary: Lookup table for converting a text y-coordinate to the high byte ; of the address of the start of the character row in screen RAM ; ; ------------------------------------------------------------------------------ ; ; In the text view, screen RAM is used to determine the colour of each on-screen ; character, so this table is used to look up the address of the colour ; information for the start of a specific text row. ; ; ****************************************************************************** .celllookh FOR I%, 0, 24 EQUB HI(SCBASE + $2003 + (40 * I%)) NEXT ; ****************************************************************************** ; ; Name: SHPPT ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw a distant ship as a point rather than a full wireframe ; ; ****************************************************************************** .SHPPT JSR EE51 ; Call EE51 to remove the ship's wireframe from the ; screen, if there is one JSR PROJ ; Project the ship onto the screen, returning: ; ; * K3(1 0) = the screen x-coordinate ; * K4(1 0) = the screen y-coordinate ; * A = K4+1 ORA K3+1 ; If either of the high bytes of the screen coordinates BNE nono ; are non-zero, jump to nono as the ship is off-screen LDA K4 ; Set A = the y-coordinate of the dot CMP #Y*2-2 ; If the y-coordinate is bigger than the y-coordinate of BCS nono ; the bottom of the screen, jump to nono as the ship's ; dot is off the bottom of the space view LDY #2 ; Call Shpt with Y = 2 to set up bytes 1-4 in the ship JSR Shpt ; lines space, aborting the call to LL9 if the dot is ; off the side of the screen. This call sets up the ; first row of the dot (i.e. a four-pixel dash) LDY #6 ; Set Y to 6 for the next call to Shpt LDA K4 ; Set A = y-coordinate of dot + 1 (so this is the second ADC #1 ; row of the two-pixel high dot) ; ; The addition works as the Shpt routine clears the C ; flag JSR Shpt ; Call Shpt with Y = 6 to set up bytes 5-8 in the ship ; lines space, aborting the call to LL9 if the dot is ; off the side of the screen. This call sets up the ; second row of the dot (i.e. another four-pixel dash, ; on the row below the first one) LDA #%00001000 ; Set bit 3 of the ship's byte #31 to record that we ORA XX1+31 ; have now drawn something on-screen for this ship STA XX1+31 LDA #8 ; Set A = 8 so when we call LL18+2 next, byte #0 of the ; heap gets set to 8, for the 8 bytes we just stuck on ; the heap JMP LL81+2 ; Call LL81+2 to draw the ship's dot, returning from the ; subroutine using a tail call PLA ; Pull the return address from the stack, so the RTS PLA ; below actually returns from the subroutine that called ; LL9 (as we called SHPPT from LL9 with a JMP) .nono LDA #%11110111 ; Clear bit 3 of the ship's byte #31 to record that AND XX1+31 ; nothing is being drawn on-screen for this ship STA XX1+31 RTS ; Return from the subroutine .Shpt ; This routine sets up four bytes in the ship line heap, ; from byte Y-1 to byte Y+2. If the ship's screen point ; turns out to be off-screen, then this routine aborts ; the entire call to LL9, exiting via nono. The four ; bytes define a horizontal four-pixel dash, for either ; the top or the bottom of the ship's dot STA (XX19),Y ; Store A in byte Y of the ship line heap (i.e. Y1) INY ; Store A in byte Y+2 of the ship line heap (i.e. Y2) INY STA (XX19),Y LDA K3 ; Set A = screen x-coordinate of the ship dot DEY ; Store A in byte Y+1 of the ship line heap (i.e. X2) STA (XX19),Y ADC #3 ; Set A = screen x-coordinate of the ship dot + 3 BCS nono-2 ; If the addition pushed the dot off the right side of ; the screen, jump to nono-2 to return from the parent ; subroutine early (i.e. LL9). This works because we ; called Shpt from above with a JSR, so nono-2 removes ; that return address from the stack, leaving the next ; return address exposed. LL9 called SHPPT with a JMP, ; so the next return address is the one that was put on ; the stack by the original call to LL9. So the RTS in ; nono will actually return us from the original call ; to LL9, thus aborting the entire drawing process DEY ; Store A in byte Y-1 of the ship line heap (i.e. X1) DEY STA (XX19),Y RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL5 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate Q = SQRT(R Q) ; Deep dive: Calculating square roots ; ; ------------------------------------------------------------------------------ ; ; Calculate the following square root: ; ; Q = SQRT(R Q) ; ; ****************************************************************************** .LL5 LDY R ; Set (Y S) = (R Q) LDA Q STA S ; So now to calculate Q = SQRT(Y S) LDX #0 ; Set X = 0, to hold the remainder STX Q ; Set Q = 0, to hold the result LDA #8 ; Set T = 8, to use as a loop counter STA T .LL6 CPX Q ; If X < Q, jump to LL7 BCC LL7 BNE P%+6 ; If X > Q, skip the next two instructions CPY #64 ; If Y < 64, jump to LL7 with the C flag clear, BCC LL7 ; otherwise fall through into LL8 with the C flag set TYA ; Set Y = Y - 64 SBC #64 ; TAY ; This subtraction will work as we know C is set from ; the BCC above, and the result will not underflow as we ; already checked that Y >= 64, so the C flag is also ; set for the next subtraction TXA ; Set X = X - Q SBC Q TAX .LL7 ROL Q ; Shift the result in Q to the left, shifting the C flag ; into bit 0 and bit 7 into the C flag ASL S ; Shift the dividend in (Y S) to the left, inserting TYA ; bit 7 from above into bit 0 ROL A TAY TXA ; Shift the remainder in X to the left ROL A TAX ASL S ; Shift the dividend in (Y S) to the left TYA ROL A TAY TXA ; Shift the remainder in X to the left ROL A TAX DEC T ; Decrement the loop counter BNE LL6 ; Loop back to LL6 until we have done 8 loops RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL28 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate R = 256 * A / Q ; Deep dive: Multiplication and division using logarithms ; ; ------------------------------------------------------------------------------ ; ; Calculate the following, where A < Q: ; ; R = 256 * A / Q ; ; This is a sister routine to LL61, which does the division when A >= Q. ; ; If A >= Q then 255 is returned and the C flag is set to indicate an overflow ; (the C flag is clear if the division was a success). ; ; The result is returned in one byte as the result of the division multiplied ; by 256, so we can return fractional results using integers. ; ; This routine uses the same logarithm algorithm that's documented in FMLTU, ; except it subtracts the logarithm values, to do a division instead of a ; multiplication. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if the answer is too big for one byte, clear if the ; division was a success ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; LL28+4 Skips the A >= Q check and always returns with C flag ; cleared, so this can be called if we know the division ; will work ; ; LL31 Skips the A >= Q check and does not set the R counter, ; so this can be used for jumping straight into the ; division loop if R is already set to 254 and we know the ; division will work ; ; ****************************************************************************** .LL28 CMP Q ; If A >= Q, then the answer will not fit in one byte, BCS LL2 ; so jump to LL2 to return 255 STA widget ; Store A in widget, so now widget = argument A TAX ; Transfer A into X, so now X = argument A BEQ LLfix ; If A = 0, jump to LLfix to return a result of 0, as ; 0 * Q / 256 is always 0 ; We now want to calculate log(A) - log(Q), first adding ; the low bytes (from the logL table), and then the high ; bytes (from the log table) LDA logL,X ; Set A = low byte of log(X) ; = low byte of log(A) (as we set X to A above) LDX Q ; Set X = Q SEC ; Set A = A - low byte of log(Q) SBC logL,X ; = low byte of log(A) - low byte of log(Q) BMI noddlog ; If the subtraction is negative, jump to noddlog LDX widget ; Set A = high byte of log(A) - high byte of log(Q) LDA log,X LDX Q SBC log,X BCS LL2 ; If the subtraction fitted into one byte and didn't ; underflow, then log(A) - log(Q) < 256, so we jump to ; LL2 return a result of 255 TAX ; Otherwise we return the A-th entry from the antilog LDA antilog,X ; table .LLfix STA R ; Set the result in R to the value of A RTS ; Return from the subroutine .noddlog LDX widget ; Set A = high byte of log(A) - high byte of log(Q) LDA log,X LDX Q SBC log,X BCS LL2 ; If the subtraction fitted into one byte and didn't ; underflow, then log(A) - log(Q) < 256, so we jump to ; LL2 to return a result of 255 TAX ; Otherwise we return the A-th entry from the antilogODD LDA antilogODD,X ; table STA R ; Set the result in R to the value of A RTS ; Return from the subroutine ;.LL28 ; These instructions are commented out in the original ;CMP Q ; source BCS LL2 ; If the subtraction fitted into one byte and didn't ; underflow, then log(A) - log(Q) < 256, so we jump to ; LL2 to return a result of 255 LDX #254 ; Otherwise set the result in R to 254 STX R .LL31 ASL A ; Shift A to the left BCS LL29 ; If bit 7 of A was set, then jump straight to the ; subtraction CMP Q ; If A < Q, skip the following subtraction BCC P%+4 SBC Q ; A >= Q, so set A = A - Q ROL R ; Rotate the counter in R to the left, and catch the ; result bit into bit 0 (which will be a 0 if we didn't ; do the subtraction, or 1 if we did) BCS LL31 ; If we still have set bits in R, loop back to LL31 to ; do the next iteration of 7 RTS ; R left with remainder of division .LL29 SBC Q ; A >= Q, so set A = A - Q SEC ; Set the C flag to rotate into the result in R ROL R ; Rotate the counter in R to the left, and catch the ; result bit into bit 0 (which will be a 0 if we didn't ; do the subtraction, or 1 if we did) BCS LL31 ; If we still have set bits in R, loop back to LL31 to ; do the next iteration of 7 LDA R ; Set A to the remainder in R RTS ; Return from the subroutine with R containing the ; remainder of the division .LL2 LDA #255 ; The division is very close to 1, so return the closest STA R ; possible answer to 256, i.e. R = 255 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL38 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (S A) = (S R) + (A Q) ; ; ------------------------------------------------------------------------------ ; ; Calculate the following between sign-magnitude numbers: ; ; (S A) = (S R) + (A Q) ; ; where the sign bytes only contain the sign bits, not magnitudes. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag Set if the addition overflowed, clear otherwise ; ; ****************************************************************************** .LL38 EOR S ; If the sign of A * S is negative, skip to LL35, as BMI LL39 ; A and S have different signs so we need to subtract LDA Q ; Otherwise set A = R + Q, which is the result we need, CLC ; as S already contains the correct sign ADC R RTS ; Return from the subroutine .LL39 LDA R ; Set A = R - Q SEC SBC Q BCC P%+4 ; If the subtraction underflowed, skip the next two ; instructions so we can negate the result CLC ; Otherwise the result is correct, and S contains the ; correct sign of the result as R is the dominant side ; of the subtraction, so clear the C flag RTS ; And return from the subroutine ; If we get here we need to negate both the result and ; the sign in S, as both are the wrong sign .LL40 PHA ; Store the result of the subtraction on the stack LDA S ; Flip the sign of S EOR #%10000000 STA S PLA ; Restore the subtraction result into A EOR #%11111111 ; Negate the result in A using two's complement, i.e. ADC #1 ; set A = ~A + 1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL51 ; Type: Subroutine ; Category: Maths (Geometry) ; Summary: Calculate the dot product of XX15 and XX16 ; ; ------------------------------------------------------------------------------ ; ; Calculate the following dot products: ; ; XX12(1 0) = XX15(5 0) . XX16(5 0) ; XX12(3 2) = XX15(5 0) . XX16(11 6) ; XX12(5 4) = XX15(5 0) . XX16(12 17) ; ; storing the results as sign-magnitude numbers in XX12 through XX12+5. ; ; When called from part 5 of LL9, XX12 contains the vector [x y z] to the ship ; we're drawing, and XX16 contains the orientation vectors, so it returns: ; ; [ x ] [ sidev_x ] [ x ] [ roofv_x ] [ x ] [ nosev_x ] ; [ y ] . [ sidev_y ] [ y ] . [ roofv_y ] [ y ] . [ nosev_y ] ; [ z ] [ sidev_z ] [ z ] [ roofv_z ] [ z ] [ nosev_z ] ; ; When called from part 6 of LL9, XX12 contains the vector [x y z] of the vertex ; we're analysing, and XX16 contains the transposed orientation vectors with ; each of them containing the x, y and z elements of the original vectors, so it ; returns: ; ; [ x ] [ sidev_x ] [ x ] [ sidev_y ] [ x ] [ sidev_z ] ; [ y ] . [ roofv_x ] [ y ] . [ roofv_y ] [ y ] . [ roofv_z ] ; [ z ] [ nosev_x ] [ z ] [ nosev_y ] [ z ] [ nosev_z ] ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; XX15(1 0) The ship (or vertex)'s x-coordinate as (x_sign x_lo) ; ; XX15(3 2) The ship (or vertex)'s y-coordinate as (y_sign y_lo) ; ; XX15(5 4) The ship (or vertex)'s z-coordinate as (z_sign z_lo) ; ; XX16 to XX16+5 The scaled sidev (or _x) vector, with: ; ; * x, y, z magnitudes in XX16, XX16+2, XX16+4 ; ; * x, y, z signs in XX16+1, XX16+3, XX16+5 ; ; XX16+6 to XX16+11 The scaled roofv (or _y) vector, with: ; ; * x, y, z magnitudes in XX16+6, XX16+8, XX16+10 ; ; * x, y, z signs in XX16+7, XX16+9, XX16+11 ; ; XX16+12 to XX16+17 The scaled nosev (or _z) vector, with: ; ; * x, y, z magnitudes in XX16+12, XX16+14, XX16+16 ; ; * x, y, z signs in XX16+13, XX16+15, XX16+17 ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; XX12(1 0) The dot product of [x y z] vector with the sidev (or _x) ; vector, with the sign in XX12+1 and magnitude in XX12 ; ; XX12(3 2) The dot product of [x y z] vector with the roofv (or _y) ; vector, with the sign in XX12+3 and magnitude in XX12+2 ; ; XX12(5 4) The dot product of [x y z] vector with the nosev (or _z) ; vector, with the sign in XX12+5 and magnitude in XX12+4 ; ; ****************************************************************************** .LL51 LDX #0 ; Set X = 0, which will contain the offset of the vector ; to use in the calculation, increasing by 6 for each ; new vector LDY #0 ; Set Y = 0, which will contain the offset of the ; result bytes in XX12, increasing by 2 for each new ; result .ll51 LDA XX15 ; Set Q = x_lo STA Q LDA XX16,X ; Set A = |sidev_x| JSR FMLTU ; Set T = A * Q / 256 STA T ; = |sidev_x| * x_lo / 256 LDA XX15+1 ; Set S to the sign of x_sign * sidev_x EOR XX16+1,X STA S LDA XX15+2 ; Set Q = y_lo STA Q LDA XX16+2,X ; Set A = |sidev_y| JSR FMLTU ; Set Q = A * Q / 256 STA Q ; = |sidev_y| * y_lo / 256 LDA T ; Set R = T STA R ; = |sidev_x| * x_lo / 256 LDA XX15+3 ; Set A to the sign of y_sign * sidev_y EOR XX16+3,X JSR LL38 ; Set (S T) = (S R) + (A Q) STA T ; = |sidev_x| * x_lo + |sidev_y| * y_lo LDA XX15+4 ; Set Q = z_lo STA Q LDA XX16+4,X ; Set A = |sidev_z| JSR FMLTU ; Set Q = A * Q / 256 STA Q ; = |sidev_z| * z_lo / 256 LDA T ; Set R = T STA R ; = |sidev_x| * x_lo + |sidev_y| * y_lo LDA XX15+5 ; Set A to the sign of z_sign * sidev_z EOR XX16+5,X JSR LL38 ; Set (S A) = (S R) + (A Q) ; = |sidev_x| * x_lo + |sidev_y| * y_lo ; + |sidev_z| * z_lo STA XX12,Y ; Store the result in XX12+Y(1 0) LDA S STA XX12+1,Y INY ; Set Y = Y + 2 INY TXA ; Set X = X + 6 CLC ADC #6 TAX CMP #17 ; If X < 17, loop back to ll51 for the next vector BCC ll51 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL9 (Part 1 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Check if ship is exploding, check if ship is in front ; Deep dive: Drawing ships ; ; ------------------------------------------------------------------------------ ; ; This routine draws the current ship on the screen. This part checks to see if ; the ship is exploding, or if it should start exploding, and if it does it sets ; things up accordingly. ; ; It also does some basic checks to see if we can see the ship, and if not it ; removes it from the screen. ; ; In this code, XX1 is used to point to the current ship's data block at INWK ; (the two labels are interchangeable). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; XX1 XX1 shares its location with INWK, which contains the ; zero-page copy of the data block for this ship from the ; K% workspace ; ; INF The address of the data block for this ship in workspace ; K% ; ; XX19(1 0) XX19(1 0) shares its location with INWK(34 33), which ; contains the ship line heap address pointer ; ; XX0 The address of the blueprint for this ship ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; EE51 Remove the current ship from the screen, called from ; SHPPT before drawing the ship as a point ; ; ****************************************************************************** .LL25 JMP PLANET ; Jump to the PLANET routine, returning from the ; subroutine using a tail call .LL9 LDA TYPE ; If the ship type is negative then this indicates a BMI LL25 ; planet or sun, so jump to PLANET via LL25 above LDA #31 ; Set XX4 = 31 to store the ship's distance for later STA XX4 ; comparison with the visibility distance. We will ; update this value below with the actual ship's ; distance if it turns out to be visible on-screen LDA NEWB ; If bit 7 of the ship's NEWB flags is set, then the BMI EE51 ; ship has been scooped or has docked, so jump down to ; EE51 to redraw its wireframe, to remove it from the ; screen LDA #%00100000 ; If bit 5 of the ship's byte #31 is set, then the ship BIT XX1+31 ; is currently exploding, so jump down to EE28 BNE EE28 BPL EE28 ; If bit 7 of the ship's byte #31 is clear then the ship ; has not just been killed, so jump down to EE28 ; Otherwise bit 5 is clear and bit 7 is set, so the ship ; is not yet exploding but it has been killed, so we ; need to start an explosion ORA XX1+31 ; Clear bits 6 and 7 of the ship's byte #31, to stop the AND #%00111111 ; ship from firing its laser and to mark it as no longer STA XX1+31 ; having just been killed LDA #0 ; Set the ship's acceleration in byte #31 to 0, updating LDY #28 ; the byte in the workspace K% data block so we don't STA (INF),Y ; have to copy it back from INWK later LDY #30 ; Set the ship's pitch counter in byte #30 to 0, to stop STA (INF),Y ; the ship from pitching JSR EE51 ; Call EE51 to remove the ship from the screen ; We now need to set up a new explosion cloud. We ; initialise it with a size of 18 (which gets increased ; by 4 every time the cloud gets redrawn), and the ; explosion count (i.e. the number of particles in the ; explosion), which go into bytes 1 and 2 of the ship ; line heap. See DOEXP for more details of explosion ; clouds LDY #1 ; Set byte #1 of the ship line heap to 18, the initial LDA #18 ; size of the explosion cloud STA (XX19),Y LDY #7 ; Fetch byte #7 from the ship's blueprint, which LDA (XX0),Y ; determines the explosion count (i.e. the number of LDY #2 ; vertices used as origins for explosion clouds), and STA (XX19),Y ; store it in byte #2 of the ship line heap ; The following loop sets bytes 3-6 of the of the ship ; line heap to random numbers .EE55 INY ; Increment Y (so the loop starts at 3) JSR DORND ; Set A and X to random numbers STA (XX19),Y ; Store A in the Y-th byte of the ship line heap CPY #6 ; Loop back until we have randomised the 6th byte BNE EE55 .EE28 LDA XX1+8 ; Set A = z_sign .EE49 BPL LL10 ; If A is positive, i.e. the ship is in front of us, ; jump down to LL10 .LL14 ; The following removes the ship from the screen by ; redrawing it (or, if it is exploding, by redrawing the ; explosion cloud). We call it when the ship is no ; longer on-screen, is too far away to be fully drawn, ; and so on LDA XX1+31 ; If bit 5 of the ship's byte #31 is clear, then the AND #%00100000 ; ship is not currently exploding, so jump down to EE51 BEQ EE51 ; to redraw its wireframe LDA XX1+31 ; The ship is exploding, so clear bit 3 of the ship's AND #%11110111 ; byte #31 to denote that the ship is no longer being STA XX1+31 ; drawn on-screen JMP DOEXP ; Jump to DOEXP to display the explosion cloud, which ; will remove it from the screen, returning from the ; subroutine using a tail call .EE51 LDA #%00001000 ; If bit 3 of the ship's byte #31 is clear, then there BIT XX1+31 ; is already nothing being shown for this ship, so BEQ LL10-1 ; return from the subroutine (as LL10-1 contains an RTS) EOR XX1+31 ; Otherwise flip bit 3 of byte #31 and store it (which STA XX1+31 ; clears bit 3 as we know it was set before the EOR), so ; this sets this ship as no longer being drawn on-screen JMP LL155 ; Jump to LL155 to draw the ship, which removes it from ; the screen, returning from the subroutine using a ; tail call RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL9 (Part 2 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Check if ship is in field of view, close enough to draw ; Deep dive: Drawing ships ; ; ------------------------------------------------------------------------------ ; ; This part checks whether the ship is in our field of view, and whether it is ; close enough to be fully drawn (if not, we jump to SHPPT to draw it as a dot). ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; LL10-1 Contains an RTS ; ; ****************************************************************************** .LL10 LDA XX1+7 ; Set A = z_hi CMP #192 ; If A >= 192 then the ship is a long way away, so jump BCS LL14 ; to LL14 to remove the ship from the screen LDA XX1 ; If x_lo >= z_lo, set the C flag, otherwise clear it CMP XX1+6 LDA XX1+1 ; Set A = x_hi - z_hi using the carry from the low SBC XX1+7 ; bytes, which sets the C flag as if we had done a full ; two-byte subtraction (x_hi x_lo) - (z_hi z_lo) BCS LL14 ; If the C flag is set then x >= z, so the ship is ; further to the side than it is in front of us, so it's ; outside our viewing angle of 45 degrees, and we jump ; to LL14 to remove it from the screen LDA XX1+3 ; If y_lo >= z_lo, set the C flag, otherwise clear it CMP XX1+6 LDA XX1+4 ; Set A = y_hi - z_hi using the carry from the low SBC XX1+7 ; bytes, which sets the C flag as if we had done a full ; two-byte subtraction (y_hi y_lo) - (z_hi z_lo) BCS LL14 ; If the C flag is set then y >= z, so the ship is ; further above us than it is in front of us, so it's ; outside our viewing angle of 45 degrees, and we jump ; to LL14 to remove it from the screen LDY #6 ; Fetch byte #6 from the ship's blueprint into X, which LDA (XX0),Y ; is the number * 4 of the vertex used for the ship's TAX ; laser LDA #255 ; Set bytes X and X+1 of the XX3 heap to 255. We're STA XX3,X ; going to use XX3 to store the screen coordinates of STA XX3+1,X ; all the visible vertices of this ship, so setting the ; laser vertex to 255 means that if we don't update this ; vertex with its screen coordinates in parts 6 and 7, ; this vertex's entry in the XX3 heap will still be 255, ; which we can check in part 9 to see if the laser ; vertex is visible (and therefore whether we should ; draw laser lines if the ship is firing at us) LDA XX1+6 ; Set (A T) = (z_hi z_lo) STA T LDA XX1+7 LSR A ; Set (A T) = (A T) / 8 ROR T LSR A ROR T LSR A ROR T LSR A ; If A >> 4 is non-zero, i.e. z_hi >= 16, jump to LL13 BNE LL13 ; as the ship is possibly far away enough to be shown as ; a dot LDA T ; Otherwise the C flag contains the previous bit 0 of A, ROR A ; which could have been set, so rotate A right four LSR A ; times so it's in the form %000xxxxx, i.e. z_hi reduced LSR A ; to a maximum value of 31 LSR A STA XX4 ; Store A in XX4, which is now the distance of the ship ; we can use for visibility testing BPL LL17 ; Jump down to LL17 (this BPL is effectively a JMP as we ; know bit 7 of A is definitely clear) .LL13 ; If we get here then the ship is possibly far enough ; away to be shown as a dot LDY #13 ; Fetch byte #13 from the ship's blueprint, which gives LDA (XX0),Y ; the ship's visibility distance, beyond which we show ; the ship as a dot CMP XX1+7 ; If z_hi <= the visibility distance, skip to LL17 to BCS LL17 ; draw the ship fully, rather than as a dot, as it is ; closer than the visibility distance LDA #%00100000 ; If bit 5 of the ship's byte #31 is set, then the AND XX1+31 ; ship is currently exploding, so skip to LL17 to draw BNE LL17 ; the ship's explosion cloud JMP SHPPT ; Otherwise jump to SHPPT to draw the ship as a dot, ; returning from the subroutine using a tail call ; ****************************************************************************** ; ; Name: LL9 (Part 3 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Set up orientation vector, ship coordinate variables ; Deep dive: Drawing ships ; ; ------------------------------------------------------------------------------ ; ; This part sets up the following variable blocks: ; ; * XX16 contains the orientation vectors, divided to normalise them ; ; * XX18 contains the ship's x, y and z coordinates in space ; ; ****************************************************************************** .LL17 LDX #5 ; First we copy the three orientation vectors into XX16, ; so set up a counter in X for the 6 bytes in each ; vector .LL15 LDA XX1+21,X ; Copy the X-th byte of sidev to the X-th byte of XX16 STA XX16,X LDA XX1+15,X ; Copy the X-th byte of roofv to XX16+6 to the X-th byte STA XX16+6,X ; of XX16+6 LDA XX1+9,X ; Copy the X-th byte of nosev to XX16+12 to the X-th STA XX16+12,X ; byte of XX16+12 DEX ; Decrement the counter BPL LL15 ; Loop back to copy the next byte of each vector, until ; we have the following: ; ; * XX16(1 0) = sidev_x ; * XX16(3 2) = sidev_y ; * XX16(5 4) = sidev_z ; ; * XX16(7 6) = roofv_x ; * XX16(9 8) = roofv_y ; * XX16(11 10) = roofv_z ; ; * XX16(13 12) = nosev_x ; * XX16(15 14) = nosev_y ; * XX16(17 16) = nosev_z LDA #197 ; Set Q = 197 STA Q LDY #16 ; Set Y to be a counter that counts down by 2 each time, ; starting with 16, then 14, 12 and so on. We use this ; to work through each of the coordinates in each of the ; orientation vectors .LL21 LDA XX16,Y ; Set A = the low byte of the vector coordinate, e.g. ; nosev_z_lo when Y = 16 ASL A ; Shift bit 7 into the C flag LDA XX16+1,Y ; Set A = the high byte of the vector coordinate, e.g. ; nosev_z_hi when Y = 16 ROL A ; Rotate A left, incorporating the C flag, so A now ; contains the original high byte, doubled, and without ; a sign bit, e.g. A = |nosev_z_hi| * 2 JSR LL28 ; Call LL28 to calculate: ; ; R = 256 * A / Q ; ; so, for nosev, this would be: ; ; R = 256 * |nosev_z_hi| * 2 / 197 ; = 2.6 * |nosev_z_hi| LDX R ; Store R in the low byte's location, so we can keep the STX XX16,Y ; old, unscaled high byte intact for the sign DEY ; Decrement the loop counter twice DEY BPL LL21 ; Loop back for the next vector coordinate until we have ; divided them all ; By this point, the vectors have been turned into ; scaled magnitudes, so we have the following: ; ; * XX16 = scaled |sidev_x| ; * XX16+2 = scaled |sidev_y| ; * XX16+4 = scaled |sidev_z| ; ; * XX16+6 = scaled |roofv_x| ; * XX16+8 = scaled |roofv_y| ; * XX16+10 = scaled |roofv_z| ; ; * XX16+12 = scaled |nosev_x| ; * XX16+14 = scaled |nosev_y| ; * XX16+16 = scaled |nosev_z| LDX #8 ; Next we copy the ship's coordinates into XX18, so set ; up a counter in X for 9 bytes .ll91 LDA XX1,X ; Copy the X-th byte from XX1 to XX18 STA XX18,X DEX ; Decrement the loop counter BPL ll91 ; Loop back for the next byte until we have copied all ; three coordinates ; So we now have the following: ; ; * XX18(2 1 0) = (x_sign x_hi x_lo) ; ; * XX18(5 4 3) = (y_sign y_hi y_lo) ; ; * XX18(8 7 6) = (z_sign z_hi z_lo) LDA #255 ; Set the 15th byte of XX2 to 255, so that face 15 is STA XX2+15 ; always visible. No ship definitions actually have this ; number of faces, but this allows us to force a vertex ; to always be visible by associating it with face 15 ; (see the ship blueprints for the Cobra Mk III at ; SHIP_COBRA_MK_3 and the asteroid at SHIP_ASTEROID for ; examples of vertices that are associated with face 15) LDY #12 ; Set Y = 12 to point to the ship blueprint byte #12, LDA XX1+31 ; If bit 5 of the ship's byte #31 is clear, then the AND #%00100000 ; ship is not currently exploding, so jump down to EE29 BEQ EE29 ; to skip the following ; Otherwise we fall through to set up the visibility ; block for an exploding ship ; ****************************************************************************** ; ; Name: LL9 (Part 4 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Set visibility for exploding ship (all faces visible) ; Deep dive: Drawing ships ; ; ------------------------------------------------------------------------------ ; ; This part sets up the visibility block in XX2 for a ship that is exploding. ; ; The XX2 block consists of one byte for each face in the ship's blueprint, ; which holds the visibility of that face. Because the ship is exploding, we ; want to set all the faces to be visible. A value of 255 in the visibility ; table means the face is visible, so the following code sets each face to 255 ; and then skips over the face visibility calculations that we would apply to a ; non-exploding ship. ; ; ****************************************************************************** LDA (XX0),Y ; Fetch byte #12 of the ship's blueprint, which contains ; the number of faces * 4 LSR A ; Set X = A / 4 LSR A ; = the number of faces TAX LDA #255 ; Set A = 255 .EE30 STA XX2,X ; Set the X-th byte of XX2 to 255 DEX ; Decrement the loop counter BPL EE30 ; Loop back for the next byte until there is one byte ; set to 255 for each face INX ; Set XX4 = 0 for the distance value we use to test STX XX4 ; for visibility, so we always shows everything .LL41 JMP LL42 ; Jump to LL42 to skip the face visibility calculations ; as we don't need to do them now we've set up the XX2 ; block for the explosion ; ****************************************************************************** ; ; Name: LL9 (Part 5 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Calculate the visibility of each of the ship's faces ; Deep dive: Drawing ships ; Back-face culling ; ; ****************************************************************************** .EE29 LDA (XX0),Y ; We set Y to 12 above before jumping down to EE29, so ; this fetches byte #12 of the ship's blueprint, which ; contains the number of faces * 4 BEQ LL41 ; If there are no faces in this ship, jump to LL42 (via ; LL41) to skip the face visibility calculations STA XX20 ; Set A = the number of faces * 4 LDY #18 ; Fetch byte #18 of the ship's blueprint, which contains LDA (XX0),Y ; the factor by which we scale the face normals, into X TAX LDA XX18+7 ; Set A = z_hi .LL90 TAY ; Set Y = z_hi BEQ LL91 ; If z_hi = 0 then jump to LL91 ; The following is a loop that jumps back to LL90+3, ; i.e. here. LL90 is only used for this loop, so it's a ; bit of a strange use of the label here INX ; Increment the scale factor in X LSR XX18+4 ; Divide (y_hi y_lo) by 2 ROR XX18+3 LSR XX18+1 ; Divide (x_hi x_lo) by 2 ROR XX18 LSR A ; Divide (z_hi z_lo) by 2 (as A contains z_hi) ROR XX18+6 TAY ; Set Y = z_hi BNE LL90+3 ; If Y is non-zero, loop back to LL90+3 to divide the ; three coordinates until z_hi is 0 .LL91 ; By this point z_hi is 0 and X contains the number of ; right shifts we had to do, plus the scale factor from ; the blueprint STX XX17 ; Store the updated scale factor in XX17 LDA XX18+8 ; Set XX15+5 = z_sign STA XX15+5 LDA XX18 ; Set XX15(1 0) = (x_sign x_lo) STA XX15 LDA XX18+2 STA XX15+1 LDA XX18+3 ; Set XX15(3 2) = (y_sign y_lo) STA XX15+2 LDA XX18+5 STA XX15+3 LDA XX18+6 ; Set XX15+4 = z_lo, so now XX15(5 4) = (z_sign z_lo) STA XX15+4 JSR LL51 ; Call LL51 to set XX12 to the dot products of XX15 and ; XX16, which we'll call dot_sidev, dot_roofv and ; dot_nosev: ; ; XX12(1 0) = [x y z] . sidev ; = (dot_sidev_sign dot_sidev_lo) ; = dot_sidev ; ; XX12(3 2) = [x y z] . roofv ; = (dot_roofv_sign dot_roofv_lo) ; = dot_roofv ; ; XX12(5 4) = [x y z] . nosev ; = (dot_nosev_sign dot_nosev_lo) ; = dot_nosev LDA XX12 ; Set XX18(2 0) = dot_sidev STA XX18 LDA XX12+1 STA XX18+2 LDA XX12+2 ; Set XX18(5 3) = dot_roofv STA XX18+3 LDA XX12+3 STA XX18+5 LDA XX12+4 ; Set XX18(8 6) = dot_nosev STA XX18+6 LDA XX12+5 STA XX18+8 LDY #4 ; Fetch byte #4 of the ship's blueprint, which contains LDA (XX0),Y ; the low byte of the offset to the faces data CLC ; Set V = low byte faces offset + XX0 ADC XX0 STA V LDY #17 ; Fetch byte #17 of the ship's blueprint, which contains LDA (XX0),Y ; the high byte of the offset to the faces data ADC XX0+1 ; Set V+1 = high byte faces offset + XX0+1 STA V+1 ; ; So V(1 0) now points to the start of the faces data ; for this ship LDY #0 ; We're now going to loop through all the faces for this ; ship, so set a counter in Y, starting from 0, which we ; will increment by 4 each loop to step through the ; four bytes of data for each face .LL86 LDA (V),Y ; Fetch byte #0 for this face into A, so: ; ; A = %xyz vvvvv, where: ; ; * Bits 0-4 = visibility distance, beyond which the ; face is always shown ; ; * Bits 7-5 = the sign bits of normal_x, normal_y ; and normal_z STA XX12+1 ; Store byte #0 in XX12+1, so XX12+1 now has the sign of ; normal_x AND #%00011111 ; Extract bits 0-4 to give the visibility distance CMP XX4 ; If XX4 <= the visibility distance, where XX4 contains BCS LL87 ; the ship's z-distance reduced to 0-31 (which we set in ; part 2), skip to LL87 as this face is close enough ; that we have to test its visibility using the face ; normals ; Otherwise this face is within range and is therefore ; always shown TYA ; Set X = Y / 4 LSR A ; = the number of this face * 4 /4 LSR A ; = the number of this face TAX LDA #255 ; Set the X-th byte of XX2 to 255 to denote that this STA XX2,X ; face is visible TYA ; Set Y = Y + 4 to point to the next face ADC #4 TAY JMP LL88 ; Jump down to LL88 to skip the following, as we don't ; need to test the face normals .LL87 LDA XX12+1 ; Fetch byte #0 for this face into A ASL A ; Shift A left and store it, so XX12+3 now has the sign STA XX12+3 ; of normal_y ASL A ; Shift A left and store it, so XX12+5 now has the sign STA XX12+5 ; of normal_z INY ; Increment Y to point to byte #1 LDA (V),Y ; Fetch byte #1 for this face and store in XX12, so STA XX12 ; XX12 = normal_x INY ; Increment Y to point to byte #2 LDA (V),Y ; Fetch byte #2 for this face and store in XX12+2, so STA XX12+2 ; XX12+2 = normal_y INY ; Increment Y to point to byte #3 LDA (V),Y ; Fetch byte #3 for this face and store in XX12+4, so STA XX12+4 ; XX12+4 = normal_z ; So we now have: ; ; XX12(1 0) = (normal_x_sign normal_x) ; ; XX12(3 2) = (normal_y_sign normal_y) ; ; XX12(5 4) = (normal_z_sign normal_z) LDX XX17 ; If XX17 < 4 then jump to LL92, otherwise we stored a CPX #4 ; larger scale factor above BCC LL92 .LL143 LDA XX18 ; Set XX15(1 0) = XX18(2 0) STA XX15 ; = dot_sidev LDA XX18+2 STA XX15+1 LDA XX18+3 ; Set XX15(3 2) = XX18(5 3) STA XX15+2 ; = dot_roofv LDA XX18+5 STA XX15+3 LDA XX18+6 ; Set XX15(5 4) = XX18(8 6) STA XX15+4 ; = dot_nosev LDA XX18+8 STA XX15+5 JMP LL89 ; Jump down to LL89 .ovflw ; If we get here then the addition below overflowed, so ; we halve the dot products and normal vector LSR XX18 ; Divide dot_sidev_lo by 2, so dot_sidev = dot_sidev / 2 LSR XX18+6 ; Divide dot_nosev_lo by 2, so dot_nosev = dot_nosev / 2 LSR XX18+3 ; Divide dot_roofv_lo by 2, so dot_roofv = dot_roofv / 2 LDX #1 ; Set X = 1 so when we fall through into LL92, we divide ; the normal vector by 2 as well .LL92 ; We jump here from above with the scale factor in X, ; and now we apply it by scaling the normal vector down ; by a factor of 2^X (i.e. divide by 2^X) LDA XX12 ; Set XX15 = normal_x STA XX15 LDA XX12+2 ; Set XX15+2 = normal_y STA XX15+2 LDA XX12+4 ; Set A = normal_z .LL93 DEX ; Decrement the scale factor in X BMI LL94 ; If X was 0 before the decrement, there is no scaling ; to do, so jump to LL94 to exit the loop LSR XX15 ; Set XX15 = XX15 / 2 ; = normal_x / 2 LSR XX15+2 ; Set XX15+2 = XX15+2 / 2 ; = normal_y / 2 LSR A ; Set A = A / 2 ; = normal_z / 2 DEX ; Decrement the scale factor in X BPL LL93+3 ; If we have more scaling to do, loop back up to the ; first LSR above until the normal vector is scaled down .LL94 STA R ; Set R = normal_z LDA XX12+5 ; Set S = normal_z_sign STA S LDA XX18+6 ; Set Q = dot_nosev_lo STA Q LDA XX18+8 ; Set A = dot_nosev_sign JSR LL38 ; Set (S A) = (S R) + (A Q) ; = normal_z + dot_nosev ; ; setting the sign of the result in S BCS ovflw ; If the addition overflowed, jump up to ovflw to divide ; both the normal vector and dot products by 2 and try ; again STA XX15+4 ; Set XX15(5 4) = (S A) LDA S ; = normal_z + dot_nosev STA XX15+5 LDA XX15 ; Set R = normal_x STA R LDA XX12+1 ; Set S = normal_x_sign STA S LDA XX18 ; Set Q = dot_sidev_lo STA Q LDA XX18+2 ; Set A = dot_sidev_sign JSR LL38 ; Set (S A) = (S R) + (A Q) ; = normal_x + dot_sidev ; ; setting the sign of the result in S BCS ovflw ; If the addition overflowed, jump up to ovflw to divide ; both the normal vector and dot products by 2 and try ; again STA XX15 ; Set XX15(1 0) = (S A) LDA S ; = normal_x + dot_sidev STA XX15+1 LDA XX15+2 ; Set R = normal_y STA R LDA XX12+3 ; Set S = normal_y_sign STA S LDA XX18+3 ; Set Q = dot_roofv_lo STA Q LDA XX18+5 ; Set A = dot_roofv_sign JSR LL38 ; Set (S A) = (S R) + (A Q) ; = normal_y + dot_roofv BCS ovflw ; If the addition overflowed, jump up to ovflw to divide ; both the normal vector and dot products by 2 and try ; again STA XX15+2 ; Set XX15(3 2) = (S A) LDA S ; = normal_y + dot_roofv STA XX15+3 .LL89 ; When we get here, we have set up the following: ; ; XX15(1 0) = normal_x + dot_sidev ; = normal_x + [x y z] . sidev ; ; XX15(3 2) = normal_y + dot_roofv ; = normal_y + [x y z] . roofv ; ; XX15(5 4) = normal_z + dot_nosev ; = normal_z + [x y z] . nosev ; ; and: ; ; XX12(1 0) = (normal_x_sign normal_x) ; ; XX12(3 2) = (normal_y_sign normal_y) ; ; XX12(5 4) = (normal_z_sign normal_z) ; ; We now calculate the dot product XX12 . XX15 to tell ; us whether or not this face is visible LDA XX12 ; Set Q = XX12 STA Q LDA XX15 ; Set A = XX15 JSR FMLTU ; Set T = A * Q / 256 STA T ; = XX15 * XX12 / 256 LDA XX12+1 ; Set S = sign of XX15(1 0) * XX12(1 0), so: EOR XX15+1 ; STA S ; (S T) = XX15(1 0) * XX12(1 0) / 256 LDA XX12+2 ; Set Q = XX12+2 STA Q LDA XX15+2 ; Set A = XX15+2 JSR FMLTU ; Set Q = A * Q STA Q ; = XX15+2 * XX12+2 / 256 LDA T ; Set T = R, so now: STA R ; ; (S R) = XX15(1 0) * XX12(1 0) / 256 LDA XX12+3 ; Set A = sign of XX15+3 * XX12+3, so: EOR XX15+3 ; ; (A Q) = XX15(3 2) * XX12(3 2) / 256 JSR LL38 ; Set (S T) = (S R) + (A Q) STA T ; = XX15(1 0) * XX12(1 0) / 256 ; + XX15(3 2) * XX12(3 2) / 256 LDA XX12+4 ; Set Q = XX12+4 STA Q LDA XX15+4 ; Set A = XX15+4 JSR FMLTU ; Set Q = A * Q STA Q ; = XX15+4 * XX12+4 / 256 LDA T ; Set T = R, so now: STA R ; ; (S R) = XX15(1 0) * XX12(1 0) / 256 ; + XX15(3 2) * XX12(3 2) / 256 LDA XX15+5 ; Set A = sign of XX15+5 * XX12+5, so: EOR XX12+5 ; ; (A Q) = XX15(5 4) * XX12(5 4) / 256 JSR LL38 ; Set (S A) = (S R) + (A Q) ; = XX15(1 0) * XX12(1 0) / 256 ; + XX15(3 2) * XX12(3 2) / 256 ; + XX15(5 4) * XX12(5 4) / 256 PHA ; Push the result A onto the stack, so the stack now ; contains the dot product XX12 . XX15 TYA ; Set X = Y / 4 LSR A ; = the number of this face * 4 /4 LSR A ; = the number of this face TAX PLA ; Pull the dot product off the stack into A BIT S ; If bit 7 of S is set, i.e. the dot product is BMI P%+4 ; negative, then this face is visible as its normal is ; pointing towards us, so skip the following instruction LDA #0 ; Otherwise the face is not visible, so set A = 0 so we ; can store this to mean "not visible" STA XX2,X ; Store the face's visibility in the X-th byte of XX2 INY ; Above we incremented Y to point to byte #3, so this ; increments Y to point to byte #4, i.e. byte #0 of the ; next face .LL88 CPY XX20 ; If Y >= XX20, the number of faces * 4, jump down to BCS LL42 ; LL42 to move on to the JMP LL86 ; Otherwise loop back to LL86 to work out the visibility ; of the next face ; ****************************************************************************** ; ; Name: LL9 (Part 6 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Calculate the visibility of each of the ship's vertices ; Deep dive: Drawing ships ; Calculating vertex coordinates ; ; ------------------------------------------------------------------------------ ; ; This section calculates the visibility of each of the ship's vertices, and for ; those that are visible, it starts the process of calculating the screen ; coordinates of each vertex ; ; ****************************************************************************** .LL42 ; The first task is to set up the inverse matrix, ready ; for us to send to the dot product routine at LL51. ; Back up in part 3, we set up the following variables: ; ; * XX16(1 0) = sidev_x ; * XX16(3 2) = sidev_y ; * XX16(5 4) = sidev_z ; ; * XX16(7 6) = roofv_x ; * XX16(9 8) = roofv_y ; * XX16(11 10) = roofv_z ; ; * XX16(13 12) = nosev_x ; * XX16(15 14) = nosev_y ; * XX16(17 16) = nosev_z ; ; and we then scaled the vectors to give the following: ; ; * XX16 = scaled |sidev_x| ; * XX16+2 = scaled |sidev_y| ; * XX16+4 = scaled |sidev_z| ; ; * XX16+6 = scaled |roofv_x| ; * XX16+8 = scaled |roofv_y| ; * XX16+10 = scaled |roofv_z| ; ; * XX16+12 = scaled |nosev_x| ; * XX16+14 = scaled |nosev_y| ; * XX16+16 = scaled |nosev_z| ; ; We now need to rearrange these locations so they ; effectively transpose the matrix into its inverse LDY XX16+2 ; Set XX16+2 = XX16+6 = scaled |roofv_x| LDX XX16+3 ; Set XX16+3 = XX16+7 = roofv_x_hi LDA XX16+6 ; Set XX16+6 = XX16+2 = scaled |sidev_y| STA XX16+2 ; Set XX16+7 = XX16+3 = sidev_y_hi LDA XX16+7 STA XX16+3 STY XX16+6 STX XX16+7 LDY XX16+4 ; Set XX16+4 = XX16+12 = scaled |nosev_x| LDX XX16+5 ; Set XX16+5 = XX16+13 = nosev_x_hi LDA XX16+12 ; Set XX16+12 = XX16+4 = scaled |sidev_z| STA XX16+4 ; Set XX16+13 = XX16+5 = sidev_z_hi LDA XX16+13 STA XX16+5 STY XX16+12 STX XX16+13 LDY XX16+10 ; Set XX16+10 = XX16+14 = scaled |nosev_y| LDX XX16+11 ; Set XX16+11 = XX16+15 = nosev_y_hi LDA XX16+14 ; Set XX16+14 = XX16+10 = scaled |roofv_z| STA XX16+10 ; Set XX16+15 = XX16+11 = roofv_z LDA XX16+15 STA XX16+11 STY XX16+14 STX XX16+15 ; So now we have the following sign-magnitude variables ; containing parts of the scaled orientation vectors: ; ; XX16(1 0) = scaled sidev_x ; XX16(3 2) = scaled roofv_x ; XX16(5 4) = scaled nosev_x ; ; XX16(7 6) = scaled sidev_y ; XX16(9 8) = scaled roofv_y ; XX16(11 10) = scaled nosev_y ; ; XX16(13 12) = scaled sidev_z ; XX16(15 14) = scaled roofv_z ; XX16(17 16) = scaled nosev_z ; ; which is what we want, as the various vectors are now ; arranged so we can use LL51 to multiply by the ; transpose (i.e. the inverse of the matrix) LDY #8 ; Fetch byte #8 of the ship's blueprint, which is the LDA (XX0),Y ; number of vertices * 8, and store it in XX20 STA XX20 ; We now set V(1 0) = XX0(1 0) + 20, so V(1 0) points ; to byte #20 of the ship's blueprint, which is always ; where the vertex data starts (i.e. just after the 20 ; byte block that define the ship's characteristics) LDA XX0 ; We start with the low bytes CLC ADC #20 STA V LDA XX0+1 ; And then do the high bytes ADC #0 STA V+1 LDY #0 ; We are about to step through all the vertices, using ; Y as a counter. There are six data bytes for each ; vertex, so we will increment Y by 6 for each iteration ; so it can act as an offset from V(1 0) to the current ; vertex's data STY CNT ; Set CNT = 0, which we will use as a pointer to the ; heap at XX3, starting it at zero so the heap starts ; out empty .LL48 STY XX17 ; Set XX17 = Y, so XX17 now contains the offset of the ; current vertex's data LDA (V),Y ; Fetch byte #0 for this vertex into XX15, so: STA XX15 ; ; XX15 = magnitude of the vertex's x-coordinate INY ; Increment Y to point to byte #1 LDA (V),Y ; Fetch byte #1 for this vertex into XX15+2, so: STA XX15+2 ; ; XX15+2 = magnitude of the vertex's y-coordinate INY ; Increment Y to point to byte #2 LDA (V),Y ; Fetch byte #2 for this vertex into XX15+4, so: STA XX15+4 ; ; XX15+4 = magnitude of the vertex's z-coordinate INY ; Increment Y to point to byte #3 LDA (V),Y ; Fetch byte #3 for this vertex into T, so: STA T ; ; T = %xyz vvvvv, where: ; ; * Bits 0-4 = visibility distance, beyond which the ; vertex is not shown ; ; * Bits 7-5 = the sign bits of x, y and z AND #%00011111 ; Extract bits 0-4 to get the visibility distance CMP XX4 ; If XX4 > the visibility distance, where XX4 contains BCC LL49-3 ; the ship's z-distance reduced to 0-31 (which we set in ; part 2), then this vertex is too far away to be ; visible, so jump down to LL50 (via the JMP instruction ; in LL49-3) to move on to the next vertex INY ; Increment Y to point to byte #4 LDA (V),Y ; Fetch byte #4 for this vertex into P, so: STA P ; ; P = %ffff ffff, where: ; ; * Bits 0-3 = the number of face 1 ; ; * Bits 4-7 = the number of face 2 AND #%00001111 ; Extract the number of face 1 into X TAX LDA XX2,X ; If XX2+X is non-zero then we decided in part 5 that BNE LL49 ; face 1 is visible, so jump to LL49 LDA P ; Fetch byte #4 for this vertex into A LSR A ; Shift right four times to extract the number of face 2 LSR A ; from bits 4-7 into X LSR A LSR A TAX LDA XX2,X ; If XX2+X is non-zero then we decided in part 5 that BNE LL49 ; face 2 is visible, so jump to LL49 INY ; Increment Y to point to byte #5 LDA (V),Y ; Fetch byte #5 for this vertex into P, so: STA P ; ; P = %ffff ffff, where: ; ; * Bits 0-3 = the number of face 3 ; ; * Bits 4-7 = the number of face 4 AND #%00001111 ; Extract the number of face 1 into X TAX LDA XX2,X ; If XX2+X is non-zero then we decided in part 5 that BNE LL49 ; face 3 is visible, so jump to LL49 LDA P ; Fetch byte #5 for this vertex into A LSR A ; Shift right four times to extract the number of face 4 LSR A ; from bits 4-7 into X LSR A LSR A TAX LDA XX2,X ; If XX2+X is non-zero then we decided in part 5 that BNE LL49 ; face 4 is visible, so jump to LL49 JMP LL50 ; If we get here then none of the four faces associated ; with this vertex are visible, so this vertex is also ; not visible, so jump to LL50 to move on to the next ; vertex .LL49 LDA T ; Fetch byte #5 for this vertex into A and store it, so STA XX15+1 ; XX15+1 now has the sign of the vertex's x-coordinate ASL A ; Shift A left and store it, so XX15+3 now has the sign STA XX15+3 ; of the vertex's y-coordinate ASL A ; Shift A left and store it, so XX15+5 now has the sign STA XX15+5 ; of the vertex's z-coordinate ; By this point we have the following: ; ; XX15(1 0) = vertex x-coordinate ; XX15(3 2) = vertex y-coordinate ; XX15(5 4) = vertex z-coordinate ; ; XX16(1 0) = scaled sidev_x ; XX16(3 2) = scaled roofv_x ; XX16(5 4) = scaled nosev_x ; ; XX16(7 6) = scaled sidev_y ; XX16(9 8) = scaled roofv_y ; XX16(11 10) = scaled nosev_y ; ; XX16(13 12) = scaled sidev_z ; XX16(15 14) = scaled roofv_z ; XX16(17 16) = scaled nosev_z JSR LL51 ; Call LL51 to set XX12 to the dot products of XX15 and ; XX16, as follows: ; ; XX12(1 0) = [ x y z ] . [ sidev_x roofv_x nosev_x ] ; ; XX12(3 2) = [ x y z ] . [ sidev_y roofv_y nosev_y ] ; ; XX12(5 4) = [ x y z ] . [ sidev_z roofv_z nosev_z ] ; ; XX12 contains the vector from the ship's centre to ; the vertex, transformed from the orientation vector ; space to the universe orientated around our ship. So ; we can refer to this vector below, let's call it ; vertv, so: ; ; vertv_x = [ x y z ] . [ sidev_x roofv_x nosev_x ] ; ; vertv_y = [ x y z ] . [ sidev_y roofv_y nosev_y ] ; ; vertv_z = [ x y z ] . [ sidev_z roofv_z nosev_z ] ; ; To finish the calculation, we now want to calculate: ; ; vertv + [ x y z ] ; ; So let's start with the vertv_x + x LDA XX1+2 ; Set A = x_sign of the ship's location STA XX15+2 ; Set XX15+2 = x_sign EOR XX12+1 ; If the sign of x_sign * the sign of vertv_x is BMI LL52 ; negative (i.e. they have different signs), skip to ; LL52 CLC ; Set XX15(2 1 0) = XX1(2 1 0) + XX12(1 0) LDA XX12 ; = (x_sign x_hi x_lo) + vertv_x ADC XX1 ; STA XX15 ; Starting with the low bytes LDA XX1+1 ; And then doing the high bytes (we can add 0 here as ADC #0 ; we know the sign byte of vertv_x is 0) STA XX15+1 JMP LL53 ; We've added the x-coordinates, so jump to LL53 to do ; the y-coordinates .LL52 ; If we get here then x_sign and vertv_x have different ; signs, so we need to subtract them to get the result LDA XX1 ; Set XX15(2 1 0) = XX1(2 1 0) - XX12(1 0) SEC ; = (x_sign x_hi x_lo) - vertv_x SBC XX12 ; STA XX15 ; Starting with the low bytes LDA XX1+1 ; And then doing the high bytes (we can subtract 0 here SBC #0 ; as we know the sign byte of vertv_x is 0) STA XX15+1 BCS LL53 ; If the subtraction didn't underflow, then the sign of ; the result is the same sign as x_sign, and that's what ; we want, so we can jump down to LL53 to do the ; y-coordinates EOR #%11111111 ; Otherwise we need to negate the result using two's STA XX15+1 ; complement, so first we flip the bits of the high byte LDA #1 ; And then subtract the low byte from 1 SBC XX15 STA XX15 BCC P%+4 ; If the above subtraction underflowed then we need to INC XX15+1 ; bump the high byte of the result up by 1 LDA XX15+2 ; And now we flip the sign of the result to get the EOR #%10000000 ; correct result STA XX15+2 .LL53 ; Now for the y-coordinates, vertv_y + y LDA XX1+5 ; Set A = y_sign of the ship's location STA XX15+5 ; Set XX15+5 = y_sign EOR XX12+3 ; If the sign of y_sign * the sign of vertv_y is BMI LL54 ; negative (i.e. they have different signs), skip to ; LL54 CLC ; Set XX15(5 4 3) = XX1(5 4 3) + XX12(3 2) LDA XX12+2 ; = (y_sign y_hi y_lo) + vertv_y ADC XX1+3 ; STA XX15+3 ; Starting with the low bytes LDA XX1+4 ; And then doing the high bytes (we can add 0 here as ADC #0 ; we know the sign byte of vertv_y is 0) STA XX15+4 JMP LL55 ; We've added the y-coordinates, so jump to LL55 to do ; the z-coordinates .LL54 ; If we get here then y_sign and vertv_y have different ; signs, so we need to subtract them to get the result LDA XX1+3 ; Set XX15(5 4 3) = XX1(5 4 3) - XX12(3 2) SEC ; = (y_sign y_hi y_lo) - vertv_y SBC XX12+2 ; STA XX15+3 ; Starting with the low bytes LDA XX1+4 ; And then doing the high bytes (we can subtract 0 here SBC #0 ; as we know the sign byte of vertv_z is 0) STA XX15+4 BCS LL55 ; If the subtraction didn't underflow, then the sign of ; the result is the same sign as y_sign, and that's what ; we want, so we can jump down to LL55 to do the ; z-coordinates EOR #%11111111 ; Otherwise we need to negate the result using two's STA XX15+4 ; complement, so first we flip the bits of the high byte LDA XX15+3 ; And then flip the bits of the low byte and add 1 EOR #%11111111 ADC #1 STA XX15+3 LDA XX15+5 ; And now we flip the sign of the result to get the EOR #%10000000 ; correct result STA XX15+5 BCC LL55 ; If the above subtraction underflowed then we need to INC XX15+4 ; bump the high byte of the result up by 1 .LL55 ; Now for the z-coordinates, vertv_z + z LDA XX12+5 ; If vertv_z_hi is negative, jump down to LL56 BMI LL56 LDA XX12+4 ; Set (U T) = XX1(7 6) + XX12(5 4) CLC ; = (z_hi z_lo) + vertv_z ADC XX1+6 ; STA T ; Starting with the low bytes LDA XX1+7 ; And then doing the high bytes (we can add 0 here as ADC #0 ; we know the sign byte of vertv_y is 0) STA U JMP LL57 ; We've added the z-coordinates, so jump to LL57 ; The adding process is continued in part 7, after a ; couple of subroutines that we don't need quite yet ; ****************************************************************************** ; ; Name: LL61 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (U R) = 256 * A / Q ; ; ------------------------------------------------------------------------------ ; ; Calculate the following, where A >= Q: ; ; (U R) = 256 * A / Q ; ; This is a sister routine to LL28, which does the division when A < Q. ; ; ****************************************************************************** .LL61 LDX Q ; If Q = 0, jump down to LL84 to return a division BEQ LL84 ; error ; The LL28 routine returns A / Q, but only if A < Q. In ; our case A >= Q, but we still want to use the LL28 ; routine, so we halve A until it's less than Q, call ; the division routine, and then double A by the same ; number of times LDX #0 ; Set X = 0 to count the number of times we halve A .LL63 LSR A ; Halve A by shifting right INX ; Increment X CMP Q ; If A >= Q, loop back to LL63 to halve it again BCS LL63 STX S ; Otherwise store the number of times we halved A in S JSR LL28 ; Call LL28 to calculate: ; ; R = 256 * A / Q ; ; which we can do now as A < Q LDX S ; Otherwise restore the number of times we halved A ; above into X LDA R ; Set A = our division result .LL64 ASL A ; Double (U A) by shifting left ROL U BMI LL84 ; If bit 7 of U is set, the doubling has overflowed, so ; jump to LL84 to return a division error DEX ; Decrement X BNE LL64 ; If X is not yet zero then we haven't done as many ; doublings as we did halvings earlier, so loop back for ; another doubling STA R ; Store the low byte of the division result in R RTS ; Return from the subroutine .LL84 LDA #50 ; If we get here then either we tried to divide by 0, or STA R ; the result overflowed, so we set U and R to 50 STA U RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL62 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate 128 - (U R) ; ; ------------------------------------------------------------------------------ ; ; Calculate the following for a positive sign-magnitude number (U R): ; ; 128 - (U R) ; ; and then store the result, low byte then high byte, on the end of the heap at ; XX3, where X points to the first free byte on the heap. Return by jumping down ; to LL66. ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; X X is incremented by 1 ; ; ****************************************************************************** .LL62 LDA #128 ; Calculate 128 - (U R), starting with the low bytes SEC SBC R STA XX3,X ; Store the low byte of the result in the X-th byte of ; the heap at XX3 INX ; Increment the heap pointer in X to point to the next ; byte LDA #0 ; And then subtract the high bytes SBC U STA XX3,X ; Store the low byte of the result in the X-th byte of ; the heap at XX3 JMP LL66 ; Jump down to LL66 ; ****************************************************************************** ; ; Name: LL9 (Part 7 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Calculate the visibility of each of the ship's vertices ; Deep dive: Drawing ships ; Calculating vertex coordinates ; ; ------------------------------------------------------------------------------ ; ; This section continues the coordinate adding from part 6 by finishing off the ; calculation that we started above: ; ; [ sidev_x roofv_x nosev_x ] [ x ] [ x ] ; vector to vertex = [ sidev_y roofv_y nosev_y ] . [ y ] + [ y ] ; [ sidev_z roofv_z nosev_z ] [ z ] [ z ] ; ; The gets stored as follows, in sign-magnitude values with the magnitudes ; fitting into the low bytes: ; ; XX15(2 0) [ x y z ] . [ sidev_x roofv_x nosev_x ] + [ x y z ] ; ; XX15(5 3) [ x y z ] . [ sidev_y roofv_y nosev_y ] + [ x y z ] ; ; (U T) [ x y z ] . [ sidev_z roofv_z nosev_z ] + [ x y z ] ; ; Finally, because this vector is from our ship to the vertex, and we are at the ; origin, this vector is the same as the coordinates of the vertex. In other ; words, we have just worked out: ; ; XX15(2 0) x-coordinate of the current vertex ; ; XX15(5 3) y-coordinate of the current vertex ; ; (U T) z-coordinate of the current vertex ; ; ****************************************************************************** .LL56 LDA XX1+6 ; Set (U T) = XX1(7 6) - XX12(5 4) SEC ; = (z_hi z_lo) - vertv_z SBC XX12+4 ; STA T ; Starting with the low bytes LDA XX1+7 ; And then doing the high bytes (we can subtract 0 here SBC #0 ; as we know the sign byte of vertv_z is 0) STA U BCC LL140 ; If the subtraction just underflowed, skip to LL140 to ; set (U T) to the minimum value of 4 BNE LL57 ; If U is non-zero, jump down to LL57 LDA T ; If T >= 4, jump down to LL57 CMP #4 BCS LL57 .LL140 LDA #0 ; If we get here then either (U T) < 4 or the STA U ; subtraction underflowed, so set (U T) = 4 LDA #4 STA T .LL57 ; By this point we have our results, so now to scale ; the 16-bit results down into 8-bit values LDA U ; If the high bytes of the result are all zero, we are ORA XX15+1 ; done, so jump down to LL60 for the next stage ORA XX15+4 BEQ LL60 LSR XX15+1 ; Shift XX15(1 0) to the right ROR XX15 LSR XX15+4 ; Shift XX15(4 3) to the right ROR XX15+3 LSR U ; Shift (U T) to the right ROR T JMP LL57 ; Jump back to LL57 to see if we can shift the result ; any more ; ****************************************************************************** ; ; Name: LL9 (Part 8 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Calculate the screen coordinates of visible vertices ; Deep dive: Drawing ships ; ; ------------------------------------------------------------------------------ ; ; This section projects the coordinate of the vertex into screen coordinates and ; stores them on the XX3 heap. By the end of this part, the XX3 heap contains ; four bytes containing the 16-bit screen coordinates of the current vertex, in ; the order: x_lo, x_hi, y_lo, y_hi. ; ; When we reach here, we are looping through the vertices, and we've just worked ; out the coordinates of the vertex in our normal coordinate system, as follows ; ; XX15(2 0) (x_sign x_lo) = x-coordinate of the current vertex ; ; XX15(5 3) (y_sign y_lo) = y-coordinate of the current vertex ; ; (U T) (z_sign z_lo) = z-coordinate of the current vertex ; ; Note that U is always zero when we get to this point, as the vertex is always ; in front of us (so it has a positive z-coordinate, into the screen). ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; LL70+1 Contains an RTS (as the first byte of an LDA ; instruction) ; ; LL66 A re-entry point into the ship-drawing routine, used by ; the LL62 routine to store 128 - (U R) on the XX3 heap ; ; ****************************************************************************** .LL60 LDA T ; Set Q = z_lo STA Q LDA XX15 ; Set A = x_lo CMP Q ; If x_lo < z_lo jump to LL69 BCC LL69 JSR LL61 ; Call LL61 to calculate: ; ; (U R) = 256 * A / Q ; = 256 * x / z ; ; which we can do as x >= z JMP LL69+3 ; Jump over the next instruction to skip the division ; for x_lo < z_lo .LL69 JSR LL28 ; Call LL28 to calculate: ; ; R = 256 * A / Q ; = 256 * x / z ; ; Because x < z, the result fits into one byte, and we ; also know that U = 0, so (U R) also contains the ; result ; At this point we have: ; ; (U R) = x / z ; ; so (U R) contains the vertex's x-coordinate projected ; on screen ; ; The next task is to convert (U R) to a pixel screen ; coordinate and stick it on the XX3 heap. ; ; We start with the x-coordinate. To convert the ; x-coordinate to a screen pixel we add 128, the ; x-coordinate of the centre of the screen, because the ; projected value is relative to an origin at the centre ; of the screen, but the origin of the screen pixels is ; at the top-left of the screen LDX CNT ; Fetch the pointer to the end of the XX3 heap from CNT ; into X LDA XX15+2 ; If x_sign is negative, jump up to LL62, which will BMI LL62 ; store 128 - (U R) on the XX3 heap and return by ; jumping down to LL66 below LDA R ; Calculate 128 + (U R), starting with the low bytes CLC ADC #128 STA XX3,X ; Store the low byte of the result in the X-th byte of ; the heap at XX3 INX ; Increment the heap pointer in X to point to the next ; byte LDA U ; And then add the high bytes ADC #0 STA XX3,X ; Store the high byte of the result in the X-th byte of ; the heap at XX3 .LL66 ; We've just stored the screen x-coordinate of the ; vertex on the XX3 heap, so now for the y-coordinate TXA ; Store the heap pointer in X on the stack (at this PHA ; it points to the last entry on the heap, not the first ; free byte) LDA #0 ; Set U = 0 STA U LDA T ; Set Q = z_lo STA Q LDA XX15+3 ; Set A = y_lo CMP Q ; If y_lo < z_lo jump to LL67 BCC LL67 JSR LL61 ; Call LL61 to calculate: ; ; (U R) = 256 * A / Q ; = 256 * y / z ; ; which we can do as y >= z JMP LL68 ; Jump to LL68 to skip the division for y_lo < z_lo .LL70 ; This gets called from below when y_sign is negative LDA #Y ; Calculate #Y + (U R), starting with the low bytes CLC ADC R STA XX3,X ; Store the low byte of the result in the X-th byte of ; the heap at XX3 INX ; Increment the heap pointer in X to point to the next ; byte LDA #0 ; And then add the high bytes ADC U STA XX3,X ; Store the high byte of the result in the X-th byte of ; the heap at XX3 JMP LL50 ; Jump to LL50 to move on to the next vertex .LL67 JSR LL28 ; Call LL28 to calculate: ; ; R = 256 * A / Q ; = 256 * y / z ; ; Because y < z, the result fits into one byte, and we ; also know that U = 0, so (U R) also contains the ; result .LL68 ; At this point we have: ; ; (U R) = y / z ; ; so (U R) contains the vertex's y-coordinate projected ; on screen ; ; We now want to convert this to a screen y-coordinate ; and stick it on the XX3 heap, much like we did with ; the x-coordinate above. Again, we convert the ; coordinate by adding or subtracting the y-coordinate ; of the centre of the screen, which is in the constant ; #Y, but this time we do the opposite, as a positive ; projected y-coordinate, i.e. up the space y-axis and ; up the screen, converts to a low y-coordinate, which ; is the opposite way round to the x-coordinates PLA ; Restore the heap pointer from the stack into X TAX INX ; When we stored the heap pointer, it pointed to the ; last entry on the heap, not the first free byte, so we ; increment it so it does point to the next free byte LDA XX15+5 ; If y_sign is negative, jump up to LL70, which will BMI LL70 ; store #Y + (U R) on the XX3 heap and return by jumping ; down to LL50 below LDA #Y ; Calculate #Y - (U R), starting with the low bytes SEC SBC R STA XX3,X ; Store the low byte of the result in the X-th byte of ; the heap at XX3 INX ; Increment the heap pointer in X to point to the next ; byte LDA #0 ; And then subtract the high bytes SBC U STA XX3,X ; Store the high byte of the result in the X-th byte of ; the heap at XX3 .LL50 ; By the time we get here, the XX3 heap contains four ; bytes containing the screen coordinates of the current ; vertex, in the order: x_lo, x_hi, y_lo, y_hi CLC ; Set CNT = CNT + 4, so the heap pointer points to the LDA CNT ; next free byte on the heap ADC #4 STA CNT LDA XX17 ; Set A to the offset of the current vertex's data, ; which we set in part 6 ADC #6 ; Set Y = A + 6, so Y now points to the data for the TAY ; next vertex BCS LL72 ; If the addition just overflowed, meaning we just tried ; to access vertex #43, jump to LL72, as the maximum ; number of vertices allowed is 42 CMP XX20 ; If Y >= number of vertices * 6 (which we stored in BCS LL72 ; XX20 in part 6), jump to LL72, as we have processed ; all the vertices for this ship JMP LL48 ; Loop back to LL48 in part 6 to calculate visibility ; and screen coordinates for the next vertex ; ****************************************************************************** ; ; Name: LL9 (Part 9 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Draw laser beams if the ship is firing its laser at us ; Deep dive: Drawing ships ; ; ------------------------------------------------------------------------------ ; ; This part sets things up so we can loop through the edges in the next part. It ; also adds a line to the ship line heap, if the ship is firing at us. ; ; When we get here, the heap at XX3 contains all the visible vertex screen ; coordinates. ; ; ****************************************************************************** .LL72 LDA XX1+31 ; If bit 5 of the ship's byte #31 is clear, then the AND #%00100000 ; ship is not currently exploding, so jump down to EE31 BEQ EE31 LDA XX1+31 ; The ship is exploding, so set bit 3 of the ship's byte ORA #%00001000 ; #31 to denote that we are drawing something on-screen STA XX1+31 ; for this ship JMP DOEXP ; Jump to DOEXP to display the explosion cloud, ; returning from the subroutine using a tail call .EE31 LDA #%00001000 ; If bit 3 of the ship's byte #31 is clear, then there BIT XX1+31 ; is nothing already being shown for this ship, so skip BEQ LL74 ; to LL74 as we don't need to erase anything from the ; screen JSR LL155 ; Otherwise call LL155 to draw the existing ship, which ; removes it from the screen LDA #%00001000 ; Set bit 3 of A so the next instruction sets bit 3 of ; the ship's byte #31 to denote that we are drawing ; something on-screen for this ship .LL74 ORA XX1+31 ; Apply bit 3 of A to the ship's byte #31, so if there STA XX1+31 ; was no ship already on screen, the bit is clear, ; otherwise it is set LDY #9 ; Fetch byte #9 of the ship's blueprint, which is the LDA (XX0),Y ; number of edges, and store it in XX20 STA XX20 LDY #0 ; We are about to step through all the edges, using Y ; as a counter STY U ; Set U = 0 (though we increment it to 1 below) STY XX17 ; Set XX17 = 0, which we are going to use as a counter ; for stepping through the ship's edges INC U ; We are going to start calculating the lines we need to ; draw for this ship, and will store them in the ship ; line heap, using U to point to the end of the heap, so ; we start by setting U = 1 BIT XX1+31 ; If bit 6 of the ship's byte #31 is clear, then the BVC LL170 ; ship is not firing its lasers, so jump to LL170 to ; skip the drawing of laser lines ; The ship is firing its laser at us, so we need to draw ; the laser lines LDA XX1+31 ; Clear bit 6 of the ship's byte #31 so the ship doesn't AND #%10111111 ; keep firing endlessly STA XX1+31 LDY #6 ; Fetch byte #6 of the ship's blueprint, which is the LDA (XX0),Y ; number * 4 of the vertex where the ship has its lasers TAY ; Put the vertex number into Y, where it can act as an ; index into list of vertex screen coordinates we added ; to the XX3 heap LDX XX3,Y ; Fetch the x_lo coordinate of the laser vertex from the STX XX15 ; XX3 heap into XX15 INX ; If X = 255 then the laser vertex is not visible, as BEQ LL170 ; the value we stored in part 2 wasn't overwritten by ; the vertex calculation in part 6 and 7, so jump to ; LL170 to skip drawing the laser lines ; We now build a laser beam from the ship's laser vertex ; towards our ship, as follows: ; ; XX15(1 0) = laser vertex x-coordinate ; ; XX15(3 2) = laser vertex y-coordinate ; ; XX15(5 4) = x-coordinate of the end of the beam ; ; XX12(1 0) = y-coordinate of the end of the beam ; ; The end of the laser beam will be positioned to look ; good, rather than being directly aimed at us, as ; otherwise we would only see a flashing point of light ; as they unleashed their attack LDX XX3+1,Y ; Fetch the x_hi coordinate of the laser vertex from the STX XX15+1 ; XX3 heap into XX15+1 INX ; If X = 255 then the laser vertex is not visible, as BEQ LL170 ; the value we stored in part 2 wasn't overwritten by ; a vertex calculation in part 6 and 7, so jump to LL170 ; to skip drawing the laser beam LDX XX3+2,Y ; Fetch the y_lo coordinate of the laser vertex from the STX XX15+2 ; XX3 heap into XX15+2 LDX XX3+3,Y ; Fetch the y_hi coordinate of the laser vertex from the STX XX15+3 ; XX3 heap into XX15+3 LDA #0 ; Set XX15(5 4) = 0, so their laser beam fires to the STA XX15+4 ; left edge of the screen STA XX15+5 STA XX12+1 ; Set XX12(1 0) = the ship's z_lo coordinate, which will LDA XX1+6 ; effectively make the vertical position of the end of STA XX12 ; the laser beam move around as the ship moves in space LDA XX1+2 ; If the ship's x_sign is positive, skip the next BPL P%+4 ; instruction DEC XX15+4 ; The ship's x_sign is negative (i.e. it's on the left ; side of the screen), so switch the laser beam so it ; goes to the right edge of the screen by decrementing ; XX15(5 4) to 255 JSR LL145 ; Call LL145 to see if the laser beam needs to be ; clipped to fit on-screen, returning the clipped line's ; end-points in (X1, Y1) and (X2, Y2) BCS LL170 ; If the C flag is set then the line is not visible on ; screen, so jump to LL170 so we don't store this line ; in the ship line heap LDY U ; Fetch the ship line heap pointer, which points to the ; next free byte on the heap, into Y LDA XX15 ; Add X1 to the end of the heap STA (XX19),Y INY ; Increment the heap pointer LDA XX15+1 ; Add Y1 to the end of the heap STA (XX19),Y INY ; Increment the heap pointer LDA XX15+2 ; Add X2 to the end of the heap STA (XX19),Y INY ; Increment the heap pointer LDA XX15+3 ; Add Y2 to the end of the heap STA (XX19),Y INY ; Increment the heap pointer STY U ; Store the updated ship line heap pointer in U ; ****************************************************************************** ; ; Name: LL9 (Part 10 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Calculate the visibility of each of the ship's edges ; Deep dive: Drawing ships ; ; ------------------------------------------------------------------------------ ; ; This part calculates which edges are visible - in other words, which lines we ; should draw - and clips them to fit on the screen. ; ; When we get here, the heap at XX3 contains all the visible vertex screen ; coordinates. ; ; ****************************************************************************** .LL170 LDY #3 ; Fetch byte #3 of the ship's blueprint, which contains CLC ; the low byte of the offset to the edges data LDA (XX0),Y ADC XX0 ; Set V = low byte edges offset + XX0 STA V LDY #16 ; Fetch byte #16 of the ship's blueprint, which contains LDA (XX0),Y ; the high byte of the offset to the edges data ADC XX0+1 ; Set V+1 = high byte edges offset + XX0+1 STA V+1 ; ; So V(1 0) now points to the start of the edges data ; for this ship LDY #5 ; Fetch byte #5 of the ship's blueprint, which contains LDA (XX0),Y ; the maximum heap size for plotting the ship (which is STA T1 ; 1 + 4 * the maximum number of visible edges) and store ; it in T1 LDY XX17 ; Set Y to the edge counter in XX17 .LL75 LDA (V),Y ; Fetch byte #0 for this edge, which contains the ; visibility distance for this edge, beyond which the ; edge is not shown CMP XX4 ; If XX4 > the visibility distance, where XX4 contains BCC LL79-3 ; the ship's z-distance reduced to 0-31 (which we set in ; part 2), then this edge is too far away to be visible, ; so jump down to LL78 (via LL79-3) to move on to the ; next edge INY ; Increment Y to point to byte #1 LDA (V),Y ; Fetch byte #1 for this edge into A, so: ; ; A = %ffff ffff, where: ; ; * Bits 0-3 = the number of face 1 ; ; * Bits 4-7 = the number of face 2 INY ; Increment Y to point to byte #2 STA P ; Store byte #1 into P AND #%00001111 ; Extract the number of face 1 into X TAX LDA XX2,X ; If XX2+X is non-zero then we decided in part 5 that BNE LL79 ; face 1 is visible, so jump to LL79 LDA P ; Fetch byte #1 for this edge into A LSR A ; Shift right four times to extract the number of face 2 LSR A ; from bits 4-7 into X LSR A LSR A TAX LDA XX2,X ; If XX2+X is non-zero then we decided in part 5 that BNE LL79 ; face 2 is visible, so skip the following instruction JMP LL78 ; Face 2 is hidden, so jump to LL78 .LL79 ; We now build the screen line for this edge, as ; follows: ; ; XX15(1 0) = start x-coordinate ; ; XX15(3 2) = start y-coordinate ; ; XX15(5 4) = end x-coordinate ; ; XX12(1 0) = end y-coordinate ; ; We can then pass this to the line clipping routine ; before storing the resulting line in the ship line ; heap LDA (V),Y ; Fetch byte #2 for this edge into X, which contains TAX ; the number of the vertex at the start of the edge ; ; Byte #2 contains the vertex number multiplied by 4, ; so we can use it as an index into the heap at XX3 to ; fetch the vertex's screen coordinates, which are ; stored as four bytes containing two 16-bit numbers INY ; Increment Y to point to byte #3 LDA (V),Y ; Fetch byte #3 for this edge into Q, which contains STA Q ; the number of the vertex at the end of the edge ; ; Byte #3 contains the vertex number multiplied by 4, ; so we can use it as an index into the heap at XX3 to ; fetch the vertex's screen coordinates, which are ; stored as four bytes containing two 16-bit numbers LDA XX3+1,X ; Fetch the x_hi coordinate of the edge's start vertex STA XX15+1 ; from the XX3 heap into XX15+1 LDA XX3,X ; Fetch the x_lo coordinate of the edge's start vertex STA XX15 ; from the XX3 heap into XX15 LDA XX3+2,X ; Fetch the y_lo coordinate of the edge's start vertex STA XX15+2 ; from the XX3 heap into XX15+2 LDA XX3+3,X ; Fetch the y_hi coordinate of the edge's start vertex STA XX15+3 ; from the XX3 heap into XX15+3 LDX Q ; Set X to the number of the vertex at the end of the ; edge, which we stored in Q LDA XX3,X ; Fetch the x_lo coordinate of the edge's end vertex STA XX15+4 ; from the XX3 heap into XX15+4 LDA XX3+3,X ; Fetch the y_hi coordinate of the edge's end vertex STA XX12+1 ; from the XX3 heap into XX12+1 LDA XX3+2,X ; Fetch the y_lo coordinate of the edge's end vertex STA XX12 ; from the XX3 heap into XX12 LDA XX3+1,X ; Fetch the x_hi coordinate of the edge's end vertex STA XX15+5 ; from the XX3 heap into XX15+5 JSR LL147 ; Call LL147 to see if the new line segment needs to be ; clipped to fit on-screen, returning the clipped line's ; end-points in (X1, Y1) and (X2, Y2) BCS LL79-3 ; If the C flag is set then the line is not visible on ; screen, so jump to LL78 (via LL79-3) so we don't store ; this line in the ship line heap JMP LL80 ; Jump down to part 11 to draw this edge ; ****************************************************************************** ; ; Name: LL145 (Part 1 of 4) ; Type: Subroutine ; Category: Drawing lines ; Summary: Clip line: Work out which end-points are on-screen, if any ; Deep dive: Line-clipping ; Extended screen coordinates ; ; ------------------------------------------------------------------------------ ; ; This routine clips the line from (x1, y1) to (x2, y2) so it fits on-screen, or ; returns an error if it can't be clipped to fit. The arguments are 16-bit ; coordinates, and the clipped line is returned using 8-bit screen coordinates. ; ; This part sets XX13 to reflect which of the two points are on-screen and ; off-screen. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; XX15(1 0) x1 as a 16-bit coordinate (x1_hi x1_lo) ; ; XX15(3 2) y1 as a 16-bit coordinate (y1_hi y1_lo) ; ; XX15(5 4) x2 as a 16-bit coordinate (x2_hi x2_lo) ; ; XX12(1 0) y2 as a 16-bit coordinate (y2_hi y2_lo) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; (X1, Y1) Screen coordinate of the start of the clipped line ; ; (X2, Y2) Screen coordinate of the end of the clipped line ; ; C flag Clear if the clipped line fits on-screen, set if it ; doesn't ; ; XX13 The state of the original coordinates on-screen: ; ; * 0 = (x2, y2) on-screen ; ; * 95 = (x1, y1) on-screen, (x2, y2) off-screen ; ; * 191 = (x1, y1) off-screen, (x2, y2) off-screen ; ; So XX13 is non-zero if the end of the line was clipped, ; meaning the next line sent to BLINE can't join onto the ; end but has to start a new segment ; ; SWAP The swap status of the returned coordinates: ; ; * $FF if we swapped the values of (x1, y1) and ; (x2, y2) as part of the clipping process ; ; * 0 if the coordinates are still in the same order ; ; Y Y is preserved ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; LL147 Don't initialise the values in SWAP or A ; ; ****************************************************************************** .LL145 LDA #0 ; Set SWAP = 0 STA SWAP LDA XX15+5 ; Set A = x2_hi .LL147 BIT dontclip ; If bit 7 of dontclip is set then line-clipping is BMI LL146 ; disabled (as this is the Short-range Chart), so jump ; to LL146 to return from the subroutine without ; clipping the line LDX #Y*2-1 ; Set X = #Y * 2 - 1. The constant #Y is 96, the ; y-coordinate of the mid-point of the space view, so ; this sets Y2 to 191, the y-coordinate of the bottom ; pixel row of the space view ORA XX12+1 ; If one or both of x2_hi and y2_hi are non-zero, jump BNE LL107 ; to LL107 to skip the following, leaving X at 191 CPX XX12 ; If y2_lo > the y-coordinate of the bottom of screen BCC LL107 ; then (x2, y2) is off the bottom of the screen, so skip ; the following instruction, leaving X at 191 LDX #0 ; Set X = 0 .LL107 STX XX13 ; Set XX13 = X, so we have: ; ; * XX13 = 0 if x2_hi = y2_hi = 0, y2_lo is on-screen ; ; * XX13 = 191 if x2_hi or y2_hi are non-zero or y2_lo ; is off the bottom of the screen ; ; In other words, XX13 is 191 if (x2, y2) is off-screen, ; otherwise it is 0 LDA XX15+1 ; If one or both of x1_hi and y1_hi are non-zero, jump ORA XX15+3 ; to LL83 BNE LL83 LDA #Y*2-1 ; If y1_lo > the y-coordinate of the bottom of screen CMP XX15+2 ; then (x1, y1) is off the bottom of the screen, so jump BCC LL83 ; to LL83 ; If we get here, (x1, y1) is on-screen LDA XX13 ; If XX13 is non-zero, i.e. (x2, y2) is off-screen, jump BNE LL109+2 ; to LL109+2 to halve it before continuing at LL83 ; If we get here, the high bytes are all zero, which ; means the x-coordinates are < 256 and therefore fit on ; screen, and neither coordinate is off the bottom of ; the screen. That means both coordinates are already on ; screen, so we don't need to do any clipping, all we ; need to do is move the low bytes into (X1, Y1) and ; X2, Y2) and return .LL146 ; If we get here then we have clipped our line to the ; screen edge (if we had to clip it at all), so we move ; the low bytes from (x1, y1) and (x2, y2) into (X1, Y1) ; and (X2, Y2), remembering that they share locations ; with XX15: ; ; X1 = XX15 ; Y1 = XX15+1 ; X2 = XX15+2 ; Y2 = XX15+3 ; ; X1 already contains x1_lo, so now we do the rest LDA XX15+2 ; Set Y1 (aka XX15+1) = y1_lo STA XX15+1 LDA XX15+4 ; Set X2 (aka XX15+2) = x2_lo STA XX15+2 LDA XX12 ; Set Y2 (aka XX15+3) = y2_lo STA XX15+3 CLC ; Clear the C flag as the clipped line fits on-screen RTS ; Return from the subroutine .LL109 SEC ; Set the C flag to indicate the clipped line does not ; fit on-screen RTS ; Return from the subroutine LSR XX13 ; If we get here then (x2, y2) is off-screen and XX13 is ; 191, so shift XX13 right to halve it to 95 ; ****************************************************************************** ; ; Name: LL145 (Part 2 of 4) ; Type: Subroutine ; Category: Drawing lines ; Summary: Clip line: Work out if any part of the line is on-screen ; Deep dive: Line-clipping ; Extended screen coordinates ; ; ------------------------------------------------------------------------------ ; ; This part does a number of tests to see if the line is on or off the screen. ; ; If we get here then at least one of (x1, y1) and (x2, y2) is off-screen, with ; XX13 set as follows: ; ; * 0 = (x1, y1) off-screen, (x2, y2) on-screen ; ; * 95 = (x1, y1) on-screen, (x2, y2) off-screen ; ; * 191 = (x1, y1) off-screen, (x2, y2) off-screen ; ; where "off-screen" is defined as having a non-zero high byte in one of the ; coordinates, or in the case of y-coordinates, having a low byte > 191, the ; y-coordinate of the bottom of the space view. ; ; ****************************************************************************** .LL83 LDA XX13 ; If XX13 < 128 then only one of the points is on-screen BPL LL115 ; so jump down to LL115 to skip the checks of whether ; both points are in the strips to the right or bottom ; of the screen ; If we get here, both points are off-screen LDA XX15+1 ; If both x1_hi and x2_hi have bit 7 set, jump to LL109 AND XX15+5 ; to return from the subroutine with the C flag set, as BMI LL109 ; the entire line is above the top of the screen LDA XX15+3 ; If both y1_hi and y2_hi have bit 7 set, jump to LL109 AND XX12+1 ; to return from the subroutine with the C flag set, as BMI LL109 ; the entire line is to the left of the screen LDX XX15+1 ; Set A = X = x1_hi - 1 DEX TXA LDX XX15+5 ; Set XX12+2 = x2_hi - 1 DEX STX XX12+2 ORA XX12+2 ; If neither (x1_hi - 1) or (x2_hi - 1) have bit 7 set, BPL LL109 ; jump to LL109 to return from the subroutine with the C ; flag set, as the line doesn't fit on-screen LDA XX15+2 ; If y1_lo < y-coordinate of screen bottom, clear the C CMP #Y*2 ; flag, otherwise set it LDA XX15+3 ; Set XX12+2 = y1_hi - (1 - C), so: SBC #0 ; STA XX12+2 ; * Set XX12+2 = y1_hi - 1 if y1_lo is on-screen ; * Set XX12+2 = y1_hi otherwise ; ; We do this subtraction because we are only interested ; in trying to move the points up by a screen if that ; might move the point into the space view portion of ; the screen, i.e. if y1_lo is on-screen LDA XX12 ; If y2_lo < y-coordinate of screen bottom, clear the C CMP #Y*2 ; flag, otherwise set it LDA XX12+1 ; Set XX12+2 = y2_hi - (1 - C), so: SBC #0 ; ; * Set XX12+1 = y2_hi - 1 if y2_lo is on-screen ; * Set XX12+1 = y2_hi otherwise ; ; We do this subtraction because we are only interested ; in trying to move the points up by a screen if that ; might move the point into the space view portion of ; the screen, i.e. if y1_lo is on-screen ORA XX12+2 ; If neither XX12+1 or XX12+2 have bit 7 set, jump to BPL LL109 ; LL109 to return from the subroutine with the C flag ; set, as the line doesn't fit on-screen ; ****************************************************************************** ; ; Name: LL145 (Part 3 of 4) ; Type: Subroutine ; Category: Drawing lines ; Summary: Clip line: Calculate the line's gradient ; Deep dive: Line-clipping ; Extended screen coordinates ; ; ****************************************************************************** .LL115 TYA ; Store Y on the stack so we can preserve it through the PHA ; call to this subroutine LDA XX15+4 ; Set XX12+2 = x2_lo - x1_lo SEC SBC XX15 STA XX12+2 LDA XX15+5 ; Set XX12+3 = x2_hi - x1_hi SBC XX15+1 STA XX12+3 LDA XX12 ; Set XX12+4 = y2_lo - y1_lo SEC SBC XX15+2 STA XX12+4 LDA XX12+1 ; Set XX12+5 = y2_hi - y1_hi SBC XX15+3 STA XX12+5 ; So we now have: ; ; delta_x in XX12(3 2) ; delta_y in XX12(5 4) ; ; where the delta is (x1, y1) - (x2, y2)) EOR XX12+3 ; Set S = the sign of delta_x * the sign of delta_y, so STA S ; if bit 7 of S is set, the deltas have different signs LDA XX12+5 ; If delta_y_hi is positive, jump down to LL110 to skip BPL LL110 ; the following LDA #0 ; Otherwise flip the sign of delta_y to make it SEC ; positive, starting with the low bytes SBC XX12+4 STA XX12+4 LDA #0 ; And then doing the high bytes, so now: SBC XX12+5 ; STA XX12+5 ; XX12(5 4) = |delta_y| .LL110 LDA XX12+3 ; If delta_x_hi is positive, jump down to LL111 to skip BPL LL111 ; the following SEC ; Otherwise flip the sign of delta_x to make it LDA #0 ; positive, starting with the low bytes SBC XX12+2 STA XX12+2 LDA #0 ; And then doing the high bytes, so now: SBC XX12+3 ; ; (A XX12+2) = |delta_x| .LL111 ; We now keep halving |delta_x| and |delta_y| until ; both of them have zero in their high bytes TAX ; If |delta_x_hi| is non-zero, skip the following BNE LL112 LDX XX12+5 ; If |delta_y_hi| = 0, jump down to LL113 (as both BEQ LL113 ; |delta_x_hi| and |delta_y_hi| are 0) .LL112 LSR A ; Halve the value of delta_x in (A XX12+2) ROR XX12+2 LSR XX12+5 ; Halve the value of delta_y XX12(5 4) ROR XX12+4 JMP LL111 ; Loop back to LL111 .LL113 ; By now, the high bytes of both |delta_x| and |delta_y| ; are zero STX T ; We know that X = 0 as that's what we tested with a BEQ ; above, so this sets T = 0 LDA XX12+2 ; If delta_x_lo < delta_y_lo, so our line is more CMP XX12+4 ; vertical than horizontal, jump to LL114 BCC LL114 ; If we get here then our line is more horizontal than ; vertical, so it is a shallow slope STA Q ; Set Q = delta_x_lo LDA XX12+4 ; Set A = delta_y_lo JSR LL28 ; Call LL28 to calculate: ; ; R = 256 * A / Q ; = 256 * delta_y_lo / delta_x_lo JMP LL116 ; Jump to LL116, as we now have the line's gradient in R .LL114 ; If we get here then our line is more vertical than ; horizontal, so it is a steep slope LDA XX12+4 ; Set Q = delta_y_lo STA Q LDA XX12+2 ; Set A = delta_x_lo JSR LL28 ; Call LL28 to calculate: ; ; R = 256 * A / Q ; = 256 * delta_x_lo / delta_y_lo DEC T ; T was set to 0 above, so this sets T = $FF when our ; line is steep ; ****************************************************************************** ; ; Name: LL145 (Part 4 of 4) ; Type: Subroutine ; Category: Drawing lines ; Summary: Clip line: Call the routine in LL188 to do the actual clipping ; Deep dive: Line-clipping ; Extended screen coordinates ; ; ------------------------------------------------------------------------------ ; ; This part sets things up to call the routine in LL188, which does the actual ; clipping. ; ; If we get here, then R has been set to the gradient of the line (x1, y1) to ; (x2, y2), with T indicating the gradient of slope: ; ; * 0 = shallow slope (more horizontal than vertical) ; ; * $FF = steep slope (more vertical than horizontal) ; ; and XX13 has been set as follows: ; ; * 0 = (x1, y1) off-screen, (x2, y2) on-screen ; ; * 95 = (x1, y1) on-screen, (x2, y2) off-screen ; ; * 191 = (x1, y1) off-screen, (x2, y2) off-screen ; ; ****************************************************************************** .LL116 LDA R ; Store the gradient in XX12+2 STA XX12+2 LDA S ; Store the type of slope in XX12+3, bit 7 clear means STA XX12+3 ; top left to bottom right, bit 7 set means top right to ; bottom left LDA XX13 ; If XX13 = 0, skip the following instruction BEQ LL138 BPL LLX117 ; If XX13 is positive, it must be 95. This means ; (x1, y1) is on-screen but (x2, y2) isn't, so we jump ; to LLX117 to swap the (x1, y1) and (x2, y2) ; coordinates around before doing the actual clipping, ; because we need to clip (x2, y2) but the clipping ; routine at LL118 only clips (x1, y1) .LL138 ; If we get here, XX13 = 0 or 191, so (x1, y1) is ; off-screen and needs clipping JSR LL118 ; Call LL118 to move (x1, y1) along the line onto the ; screen, i.e. clip the line at the (x1, y1) end LDA XX13 ; If XX13 = 0, i.e. (x2, y2) is on-screen, jump down to BPL LL124 ; LL124 to return with a successfully clipped line .LL117 ; If we get here, XX13 = 191 (both coordinates are ; off-screen) LDA XX15+1 ; If either of x1_hi or y1_hi are non-zero, jump to ORA XX15+3 ; LL137 to return from the subroutine with the C flag BNE LL137 ; set, as the line doesn't fit on-screen LDA XX15+2 ; If y1_lo > y-coordinate of the bottom of the screen CMP #Y*2 ; jump to LL137 to return from the subroutine with the BCS LL137 ; C flag set, as the line doesn't fit on-screen .LLX117 ; If we get here, XX13 = 95 or 191, and in both cases ; (x2, y2) is off-screen, so we now need to swap the ; (x1, y1) and (x2, y2) coordinates around before doing ; the actual clipping, because we need to clip (x2, y2) ; but the clipping routine at LL118 only clips (x1, y1) LDX XX15 ; Swap x1_lo = x2_lo LDA XX15+4 STA XX15 STX XX15+4 LDA XX15+5 ; Swap x2_lo = x1_lo LDX XX15+1 STX XX15+5 STA XX15+1 LDX XX15+2 ; Swap y1_lo = y2_lo LDA XX12 STA XX15+2 STX XX12 LDA XX12+1 ; Swap y2_lo = y1_lo LDX XX15+3 STX XX12+1 STA XX15+3 JSR LL118 ; Call LL118 to move (x1, y1) along the line onto the ; screen, i.e. clip the line at the (x1, y1) end DEC SWAP ; Set SWAP = $FF to indicate that we just clipped the ; line at the (x2, y2) end by swapping the coordinates ; (the DEC does this as we set SWAP to 0 at the start of ; this subroutine) .LL124 PLA ; Restore Y from the stack so it gets preserved through TAY ; the call to this subroutine JMP LL146 ; Jump up to LL146 to move the low bytes of (x1, y1) and ; (x2, y2) into (X1, Y1) and (X2, Y2), and return from ; the subroutine with a successfully clipped line .LL137 PLA ; Restore Y from the stack so it gets preserved through TAY ; the call to this subroutine SEC ; Set the C flag to indicate the clipped line does not ; fit on-screen RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL9 (Part 11 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Add all visible edges to the ship line heap ; Deep dive: Drawing ships ; ; ------------------------------------------------------------------------------ ; ; This part adds all the visible edges to the ship line heap, so we can draw ; them in part 12. ; ; Other entry points: ; ; LL81+2 Draw the contents of the ship line heap, used to draw ; the ship as a dot from SHPPT ; ; ****************************************************************************** .LL80 LDY U ; Fetch the ship line heap pointer, which points to the ; next free byte on the heap, into Y LDA XX15 ; Add X1 to the end of the heap STA (XX19),Y INY ; Increment the heap pointer LDA XX15+1 ; Add Y1 to the end of the heap STA (XX19),Y INY ; Increment the heap pointer LDA XX15+2 ; Add X2 to the end of the heap STA (XX19),Y INY ; Increment the heap pointer LDA XX15+3 ; Add Y2 to the end of the heap STA (XX19),Y INY ; Increment the heap pointer STY U ; Store the updated ship line heap pointer in U CPY T1 ; If Y >= T1 then we have reached the maximum number of BCS LL81 ; edge lines that we can store in the ship line heap, so ; skip to LL81 so we don't loop back for the next edge .LL78 INC XX17 ; Increment the edge counter to point to the next edge LDY XX17 ; If Y >= XX20, which contains the number of edges in CPY XX20 ; the blueprint, jump to LL81 as we have processed all BCS LL81 ; the edges and don't need to loop back for the next one LDY #0 ; Set Y to point to byte #0 again, ready for the next ; edge LDA V ; Increment V by 4 so V(1 0) points to the data for the ADC #4 ; next edge STA V BCC ll81 ; If the above addition didn't overflow, jump to ll81 to ; skip the following instruction INC V+1 ; Otherwise increment the high byte of V(1 0), as we ; just moved the V(1 0) pointer past a page boundary .ll81 JMP LL75 ; Loop back to LL75 to process the next edge .LL81 ; We have finished adding lines to the ship line heap, ; so now we need to set the first byte of the heap to ; the number of bytes stored there LDA U ; Fetch the ship line heap pointer from U into A, which ; points to the end of the heap, and therefore contains ; the heap size LDY #0 ; Store A as the first byte of the ship line heap, so STA (XX19),Y ; the heap is now correctly set up ; ****************************************************************************** ; ; Name: LL9 (Part 12 of 12) ; Type: Subroutine ; Category: Drawing ships ; Summary: Draw ship: Draw all the visible edges from the ship line heap ; Deep dive: Drawing ships ; ; ------------------------------------------------------------------------------ ; ; This part draws the lines in the ship line heap, which is used both to draw ; the ship, and to remove it from the screen. ; ; ****************************************************************************** .LL155 LDY #0 ; Fetch the first byte from the ship line heap into A, LDA (XX19),Y ; which contains the number of bytes in the heap STA XX20 ; Store the heap size in XX20 CMP #4 ; If the heap size is less than 4, there is nothing to BCC LL82 ; draw, so return from the subroutine (as LL82 contains ; an RTS) INY ; Set Y = 1, which we will use as an index into the ship ; line heap, starting at byte #1 (as byte #0 contains ; the heap size) .LL27 LDA (XX19),Y ; Fetch the X1 line coordinate from the heap and store STA XX15 ; it in XX15 INY ; Increment the heap pointer LDA (XX19),Y ; Fetch the Y1 line coordinate from the heap and store STA XX15+1 ; it in XX15+1 INY ; Increment the heap pointer LDA (XX19),Y ; Fetch the X2 line coordinate from the heap and store STA XX15+2 ; it in XX15+2 INY ; Increment the heap pointer LDA (XX19),Y ; Fetch the Y2 line coordinate from the heap and store STA XX15+3 ; it in XX15+3 JSR LL30 ; Draw a line from (X1, Y1) to (X2, Y2) INY ; Increment the heap pointer CPY XX20 ; If the heap counter is less than the size of the heap, BCC LL27 ; loop back to LL27 to draw the next line from the heap .LL82 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL118 ; Type: Subroutine ; Category: Drawing lines ; Summary: Move a point along a line until it is on-screen ; Deep dive: Line-clipping ; ; ------------------------------------------------------------------------------ ; ; Given a point (x1, y1), a gradient and a direction of slope, move the point ; along the line until it is on-screen, so this effectively clips the (x1, y1) ; end of a line to be on the screen. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; XX15(1 0) x1 as a 16-bit coordinate (x1_hi x1_lo) ; ; XX15(3 2) y1 as a 16-bit coordinate (y1_hi y1_lo) ; ; XX12+2 The line's gradient * 256 (so 1.0 = 256) ; ; XX12+3 The direction of slope: ; ; * Positive (bit 7 clear) = top left to bottom right ; ; * Negative (bit 7 set) = top right to bottom left ; ; T The gradient of slope: ; ; * 0 if it's a shallow slope ; ; * $FF if it's a steep slope ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; XX15 x1 as an 8-bit coordinate ; ; XX15+2 y1 as an 8-bit coordinate ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; LL118-1 Contains an RTS ; ; ****************************************************************************** .LL118 LDA XX15+1 ; If x1_hi is positive, jump down to LL119 to skip the BPL LL119 ; following STA S ; Otherwise x1_hi is negative, i.e. off the left of the ; screen, so set S = x1_hi JSR LL120 ; Call LL120 to calculate: ; ; (Y X) = (S x1_lo) * XX12+2 if T = 0 ; = x1 * gradient ; ; (Y X) = (S x1_lo) / XX12+2 if T <> 0 ; = x1 / gradient ; ; with the sign of (Y X) set to the opposite of the ; line's direction of slope TXA ; Set y1 = y1 + (Y X) CLC ; ADC XX15+2 ; starting with the low bytes STA XX15+2 TYA ; And then adding the high bytes ADC XX15+3 STA XX15+3 LDA #0 ; Set x1 = 0 STA XX15 STA XX15+1 TAX ; Set X = 0 so the next instruction becomes a JMP .LL119 BEQ LL134 ; If x1_hi = 0 then jump down to LL134 to skip the ; following, as the x-coordinate is already on-screen ; (as 0 <= (x_hi x_lo) <= 255) STA S ; Otherwise x1_hi is positive, i.e. x1 >= 256 and off DEC S ; the right side of the screen, so set S = x1_hi - 1 JSR LL120 ; Call LL120 to calculate: ; ; (Y X) = (S x1_lo) * XX12+2 if T = 0 ; = (x1 - 256) * gradient ; ; (Y X) = (S x1_lo) / XX12+2 if T <> 0 ; = (x1 - 256) / gradient ; ; with the sign of (Y X) set to the opposite of the ; line's direction of slope TXA ; Set y1 = y1 + (Y X) CLC ; ADC XX15+2 ; starting with the low bytes STA XX15+2 TYA ; And then adding the high bytes ADC XX15+3 STA XX15+3 LDX #255 ; Set x1 = 255 STX XX15 INX STX XX15+1 .LL134 ; We have moved the point so the x-coordinate is on ; screen (i.e. in the range 0-255), so now for the ; y-coordinate LDA XX15+3 ; If y1_hi is positive, jump down to LL119 to skip BPL LL135 ; the following STA S ; Otherwise y1_hi is negative, i.e. off the top of the ; screen, so set S = y1_hi LDA XX15+2 ; Set R = y1_lo STA R JSR LL123 ; Call LL123 to calculate: ; ; (Y X) = (S R) / XX12+2 if T = 0 ; = y1 / gradient ; ; (Y X) = (S R) * XX12+2 if T <> 0 ; = y1 * gradient ; ; with the sign of (Y X) set to the opposite of the ; line's direction of slope TXA ; Set x1 = x1 + (Y X) CLC ; ADC XX15 ; starting with the low bytes STA XX15 TYA ; And then adding the high bytes ADC XX15+1 STA XX15+1 LDA #0 ; Set y1 = 0 STA XX15+2 STA XX15+3 .LL135 ;BNE LL139 ; This instruction is commented out in the original ; source LDA XX15+2 ; Set (S R) = (y1_hi y1_lo) - screen height SEC ; SBC #Y*2 ; starting with the low bytes STA R LDA XX15+3 ; And then subtracting the high bytes SBC #0 STA S BCC LL136 ; If the subtraction underflowed, i.e. if y1 < screen ; height, then y1 is already on-screen, so jump to LL136 ; to return from the subroutine, as we are done .LL139 ; If we get here then y1 >= screen height, i.e. off the ; bottom of the screen JSR LL123 ; Call LL123 to calculate: ; ; (Y X) = (S R) / XX12+2 if T = 0 ; = (y1 - screen height) / gradient ; ; (Y X) = (S R) * XX12+2 if T <> 0 ; = (y1 - screen height) * gradient ; ; with the sign of (Y X) set to the opposite of the ; line's direction of slope TXA ; Set x1 = x1 + (Y X) CLC ; ADC XX15 ; starting with the low bytes STA XX15 TYA ; And then adding the high bytes ADC XX15+1 STA XX15+1 LDA #Y*2-1 ; Set y1 = 2 * #Y - 1. The constant #Y is 96, the STA XX15+2 ; y-coordinate of the mid-point of the space view, so LDA #0 ; this sets Y2 to 191, the y-coordinate of the bottom STA XX15+3 ; pixel row of the space view .LL136 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL120 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (Y X) = (S x1_lo) * XX12+2 or (S x1_lo) / XX12+2 ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; * If T = 0, this is a shallow slope, so calculate (Y X) = (S x1_lo) * XX12+2 ; ; * If T <> 0, this is a steep slope, so calculate (Y X) = (S x1_lo) / XX12+2 ; ; giving (Y X) the opposite sign to the slope direction in XX12+3. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; T The gradient of slope: ; ; * 0 if it's a shallow slope ; ; * $FF if it's a steep slope ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; LL122 Calculate (Y X) = (S R) * Q and set the sign to the ; opposite of the top byte on the stack ; ; ****************************************************************************** .LL120 LDA XX15 ; Set R = x1_lo STA R ;.LL120 ; This label is commented out in the original source JSR LL129 ; Call LL129 to do the following: ; ; Q = XX12+2 ; = line gradient ; ; A = S EOR XX12+3 ; = S EOR slope direction ; ; (S R) = |S R| ; ; So A contains the sign of S * slope direction PHA ; Store A on the stack so we can use it later LDX T ; If T is non-zero, then it's a steep slope, so jump BNE LL121 ; down to LL121 to calculate this instead: ; ; (Y X) = (S R) / Q .LL122 ; The following calculates: ; ; (Y X) = (S R) * Q ; ; using the same shift-and-add algorithm that's ; documented in MULT1 LDA #0 ; Set A = 0 TAX ; Set (Y X) = 0 so we can start building the answer here TAY LSR S ; Shift (S R) to the right, so we extract bit 0 of (S R) ROR R ; into the C flag ASL Q ; Shift Q to the left, catching bit 7 in the C flag BCC LL126 ; If C (i.e. the next bit from Q) is clear, do not do ; the addition for this bit of Q, and instead skip to ; LL126 to just do the shifts .LL125 TXA ; Set (Y X) = (Y X) + (S R) CLC ; ADC R ; starting with the low bytes TAX TYA ; And then doing the high bytes ADC S TAY .LL126 LSR S ; Shift (S R) to the right ROR R ASL Q ; Shift Q to the left, catching bit 7 in the C flag BCS LL125 ; If C (i.e. the next bit from Q) is set, loop back to ; LL125 to do the addition for this bit of Q BNE LL126 ; If Q has not yet run out of set bits, loop back to ; LL126 to do the "shift" part of shift-and-add until ; we have done additions for all the set bits in Q, to ; give us our multiplication result PLA ; Restore A, which we calculated above, from the stack BPL LL133 ; If A is positive jump to LL133 to negate (Y X) and ; return from the subroutine using a tail call RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL123 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate (Y X) = (S R) / XX12+2 or (S R) * XX12+2 ; ; ------------------------------------------------------------------------------ ; ; Calculate the following: ; ; * If T = 0, this is a shallow slope, so calculate (Y X) = (S R) / XX12+2 ; ; * If T <> 0, this is a steep slope, so calculate (Y X) = (S R) * XX12+2 ; ; giving (Y X) the opposite sign to the slope direction in XX12+3. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; XX12+2 The line's gradient * 256 (so 1.0 = 256) ; ; XX12+3 The direction of slope: ; ; * Bit 7 clear means top left to bottom right ; ; * Bit 7 set means top right to bottom left ; ; T The gradient of slope: ; ; * 0 if it's a shallow slope ; ; * $FF if it's a steep slope ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; LL121 Calculate (Y X) = (S R) / Q and set the sign to the ; opposite of the top byte on the stack ; ; LL133 Negate (Y X) and return from the subroutine ; ; LL128 Contains an RTS ; ; ****************************************************************************** .LL123 JSR LL129 ; Call LL129 to do the following: ; ; Q = XX12+2 ; = line gradient ; ; A = S EOR XX12+3 ; = S EOR slope direction ; ; (S R) = |S R| ; ; So A contains the sign of S * slope direction PHA ; Store A on the stack so we can use it later LDX T ; If T is non-zero, then it's a steep slope, so jump up BNE LL122 ; to LL122 to calculate this instead: ; ; (Y X) = (S R) * Q .LL121 ; The following calculates: ; ; (Y X) = (S R) / Q ; ; using the same shift-and-subtract algorithm that's ; documented in TIS2 LDA #%11111111 ; Set Y = %11111111 TAY ASL A ; Set X = %11111110 TAX ; This sets (Y X) = %1111111111111110, so we can rotate ; through 15 loop iterations, getting a 1 each time, and ; then getting a 0 on the 16th iteration... and we can ; also use it to catch our result bits into bit 0 each ; time .LL130 ASL R ; Shift (S R) to the left ROL S LDA S ; Set A = S BCS LL131 ; If bit 7 of S was set, then jump straight to the ; subtraction CMP Q ; If A < Q (i.e. S < Q), skip the following subtractions BCC LL132 .LL131 SBC Q ; A >= Q (i.e. S >= Q) so set: STA S ; ; S = (A R) - Q ; = (S R) - Q ; ; starting with the low bytes (we know the C flag is ; set so the subtraction will be correct) LDA R ; And then doing the high bytes SBC #0 STA R SEC ; Set the C flag to rotate into the result in (Y X) .LL132 TXA ; Rotate the counter in (Y X) to the left, and catch the ROL A ; result bit into bit 0 (which will be a 0 if we didn't TAX ; do the subtraction, or 1 if we did) TYA ROL A TAY BCS LL130 ; If we still have set bits in (Y X), loop back to LL130 ; to do the next iteration of 15, until we have done the ; whole division PLA ; Restore A, which we calculated above, from the stack BMI LL128 ; If A is negative jump to LL128 to return from the ; subroutine with (Y X) as is .LL133 TXA ; Otherwise negate (Y X) using two's complement by first EOR #%11111111 ; setting the low byte to ~X + 1 ;CLC ; ADC #1 ; The CLC instruction is commented out in the original TAX ; source. It would have no effect as we know the C flag ; is clear from when we passed through the BCS above TYA ; Then set the high byte to ~Y + C EOR #%11111111 ADC #0 TAY .LL128 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LL129 ; Type: Subroutine ; Category: Maths (Arithmetic) ; Summary: Calculate Q = XX12+2, A = S EOR XX12+3 and (S R) = |S R| ; ; ------------------------------------------------------------------------------ ; ; Do the following, in this order: ; ; Q = XX12+2 ; ; A = S EOR XX12+3 ; ; (S R) = |S R| ; ; This sets up the variables required above to calculate (S R) / XX12+2 and give ; the result the opposite sign to XX12+3. ; ; ****************************************************************************** .LL129 LDX XX12+2 ; Set Q = XX12+2 STX Q LDA S ; If S is positive, jump to LL127 BPL LL127 LDA #0 ; Otherwise set R = -R SEC SBC R STA R LDA S ; Push S onto the stack PHA EOR #%11111111 ; Set S = ~S + 1 + C ADC #0 STA S PLA ; Pull the original, negative S from the stack into A .LL127 EOR XX12+3 ; Set A = original argument S EOR'd with XX12+3 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Save ELTG.bin ; ; ****************************************************************************** PRINT "ELITE G" PRINT "Assembled at ", ~CODE_G% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_G%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_G% PRINT "S.ELTG ", ~CODE_G%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_G% SAVE "3-assembled-output/ELTG.bin", CODE_G%, P%, LOAD% ; ****************************************************************************** ; ; ELITE H FILE ; ; Produces the binary file ELTH.bin that gets loaded by elite-checksum.py. ; ; ****************************************************************************** CODE_H% = P% LOAD_H% = LOAD% + P% - CODE% ; ****************************************************************************** ; ; Name: MVEIT (Part 1 of 9) ; Type: Subroutine ; Category: Moving ; Summary: Move current ship: Tidy the orientation vectors ; Deep dive: Program flow of the ship-moving routine ; Scheduling tasks with the main loop counter ; ; ------------------------------------------------------------------------------ ; ; This routine has multiple stages. This stage does the following: ; ; * Tidy the orientation vectors for one of the ship slots ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; INWK The current ship/planet/sun's data block ; ; XSAV The slot number of the current ship/planet/sun ; ; TYPE The type of the current ship/planet/sun ; ; ****************************************************************************** .MVEIT LDA INWK+31 ; If bits 5 or 7 of ship byte #31 are set, jump to MV30 AND #%10100000 ; as the ship is either exploding or has been killed, so BNE MV30 ; we don't need to tidy its orientation vectors or apply ; tactics LDA MCNT ; Fetch the main loop counter EOR XSAV ; Fetch the slot number of the ship we are moving, EOR AND #15 ; with the loop counter and apply mod 15 to the result. BNE MV3 ; The result will be zero when "counter mod 15" matches ; the slot number, so this makes sure we call TIDY 12 ; times every 16 main loop iterations, like this: ; ; Iteration 0, tidy the ship in slot 0 ; Iteration 1, tidy the ship in slot 1 ; Iteration 2, tidy the ship in slot 2 ; ... ; Iteration 11, tidy the ship in slot 11 ; Iteration 12, do nothing ; Iteration 13, do nothing ; Iteration 14, do nothing ; Iteration 15, do nothing ; Iteration 16, tidy the ship in slot 0 ; ... ; ; and so on JSR TIDY ; Call TIDY to tidy up the orientation vectors, to ; prevent the ship from getting elongated and out of ; shape due to the imprecise nature of trigonometry ; in assembly language ; ****************************************************************************** ; ; Name: MVEIT (Part 2 of 9) ; Type: Subroutine ; Category: Moving ; Summary: Move current ship: Call tactics routine, remove ship from scanner ; Deep dive: Scheduling tasks with the main loop counter ; ; ------------------------------------------------------------------------------ ; ; This routine has multiple stages. This stage does the following: ; ; * Apply tactics to ships with AI enabled (by calling the TACTICS routine) ; ; * Remove the ship from the scanner, so we can move it ; ; ****************************************************************************** .MV3 LDX TYPE ; If the type of the ship we are moving is positive, BPL P%+5 ; i.e. it is not a planet (types 128 and 130) or sun ; (type 129), then skip the following instruction JMP MV40 ; This item is the planet or sun, so jump to MV40 to ; move it, which ends by jumping back into this routine ; at MV45 (after all the rotation, tactics and scanner ; code, which we don't need to apply to planets or suns) LDA INWK+32 ; Fetch the ship's byte #32 (AI flag) into A BPL MV30 ; If bit 7 of the AI flag is clear, then skip the ; following as AI is disabled and the ship has no ; tactics CPX #MSL ; If the ship is a missile, skip straight to MV26 to BEQ MV26 ; call the TACTICS routine, as we do this every ; iteration of the main loop for missiles only LDA MCNT ; Fetch the main loop counter EOR XSAV ; Fetch the slot number of the ship we are moving, EOR AND #7 ; with the loop counter and apply mod 8 to the result. BNE MV30 ; The result will be zero when "counter mod 8" matches ; the slot number mod 8, so this makes sure we call ; TACTICS 12 times every 8 main loop iterations, like ; this: ; ; Iteration 0, apply tactics to slots 0 and 8 ; Iteration 1, apply tactics to slots 1 and 9 ; Iteration 2, apply tactics to slots 2 and 10 ; Iteration 3, apply tactics to slots 3 and 11 ; Iteration 4, apply tactics to slot 4 ; Iteration 5, apply tactics to slot 5 ; Iteration 6, apply tactics to slot 6 ; Iteration 7, apply tactics to slot 7 ; Iteration 8, apply tactics to slots 0 and 8 ; ... ; ; and so on .MV26 JSR TACTICS ; Call TACTICS to apply AI tactics to this ship .MV30 JSR SCAN ; Draw the ship on the scanner, which has the effect of ; removing it as it hasn't yet moved ; ****************************************************************************** ; ; Name: MVEIT (Part 3 of 9) ; Type: Subroutine ; Category: Moving ; Summary: Move current ship: Move ship forward according to its speed ; ; ------------------------------------------------------------------------------ ; ; This routine has multiple stages. This stage does the following: ; ; * Move the ship forward (along the vector pointing in the direction of ; travel) according to its speed: ; ; (x, y, z) += nosev_hi * speed / 64 ; ; ****************************************************************************** LDA INWK+27 ; Set Q = the ship's speed byte #27 * 4 ASL A ASL A STA Q LDA INWK+10 ; Set A = |nosev_x_hi| AND #%01111111 JSR FMLTU ; Set R = A * Q / 256 STA R ; = |nosev_x_hi| * speed / 64 LDA INWK+10 ; If nosev_x_hi is positive, then: LDX #0 ; JSR MVT1-2 ; (x_sign x_hi x_lo) = (x_sign x_hi x_lo) + R ; ; If nosev_x_hi is negative, then: ; ; (x_sign x_hi x_lo) = (x_sign x_hi x_lo) - R ; ; So in effect, this does: ; ; (x_sign x_hi x_lo) += nosev_x_hi * speed / 64 LDA INWK+12 ; Set A = |nosev_y_hi| AND #%01111111 JSR FMLTU ; Set R = A * Q / 256 STA R ; = |nosev_y_hi| * speed / 64 LDA INWK+12 ; If nosev_y_hi is positive, then: LDX #3 ; JSR MVT1-2 ; (y_sign y_hi y_lo) = (y_sign y_hi y_lo) + R ; ; If nosev_y_hi is negative, then: ; ; (y_sign y_hi y_lo) = (y_sign y_hi y_lo) - R ; ; So in effect, this does: ; ; (y_sign y_hi y_lo) += nosev_y_hi * speed / 64 LDA INWK+14 ; Set A = |nosev_z_hi| AND #%01111111 JSR FMLTU ; Set R = A * Q / 256 STA R ; = |nosev_z_hi| * speed / 64 LDA INWK+14 ; If nosev_y_hi is positive, then: LDX #6 ; JSR MVT1-2 ; (z_sign z_hi z_lo) = (z_sign z_hi z_lo) + R ; ; If nosev_z_hi is negative, then: ; ; (z_sign z_hi z_lo) = (z_sign z_hi z_lo) - R ; ; So in effect, this does: ; ; (z_sign z_hi z_lo) += nosev_z_hi * speed / 64 ; ****************************************************************************** ; ; Name: MVEIT (Part 4 of 9) ; Type: Subroutine ; Category: Moving ; Summary: Move current ship: Apply acceleration to ship's speed as a one-off ; ; ------------------------------------------------------------------------------ ; ; This routine has multiple stages. This stage does the following: ; ; * Apply acceleration to the ship's speed (if acceleration is non-zero), ; and then zero the acceleration as it's a one-off change ; ; ****************************************************************************** LDA INWK+27 ; Set A = the ship's speed in byte #24 + the ship's CLC ; acceleration in byte #28 ADC INWK+28 BPL P%+4 ; If the result is positive, skip the following ; instruction LDA #0 ; Set A to 0 to stop the speed from going negative LDY #15 ; We now fetch byte #15 from the ship's blueprint, which ; contains the ship's maximum speed, so set Y = 15 to ; use as an index CMP (XX0),Y ; If A < the ship's maximum speed, skip the following BCC P%+4 ; instruction LDA (XX0),Y ; Set A to the ship's maximum speed STA INWK+27 ; We have now calculated the new ship's speed after ; accelerating and keeping the speed within the ship's ; limits, so store the updated speed in byte #27 LDA #0 ; We have added the ship's acceleration, so we now set STA INWK+28 ; it back to 0 in byte #28, as it's a one-off change ; ****************************************************************************** ; ; Name: MVEIT (Part 5 of 9) ; Type: Subroutine ; Category: Moving ; Summary: Move current ship: Rotate ship's location by our pitch and roll ; Deep dive: Rotating the universe ; ; ------------------------------------------------------------------------------ ; ; This routine has multiple stages. This stage does the following: ; ; * Rotate the ship's location in space by the amount of pitch and roll of ; our ship ; ; We implement this as follows: ; ; 1. K2 = y - alpha * x ; 2. z = z + beta * K2 ; 3. y = K2 - beta * z ; 4. x = x + alpha * y ; ; ****************************************************************************** LDX ALP1 ; Fetch the magnitude of the current roll into X, so ; if the roll angle is alpha, X contains |alpha| LDA INWK ; Set P = ~x_lo (i.e. with all its bits flipped) so that EOR #%11111111 ; we can pass x_lo to MLTU2 below) STA P LDA INWK+1 ; Set A = x_hi JSR MLTU2-2 ; Set (A P+1 P) = (A ~P) * X ; = (x_hi x_lo) * alpha STA P+2 ; Store the high byte of the result in P+2, so we now ; have: ; ; P(2 1 0) = (x_hi x_lo) * alpha LDA ALP2+1 ; Fetch the flipped sign of the current roll angle alpha EOR INWK+2 ; from ALP2+1 and EOR with byte #2 (x_sign), so if the ; flipped roll angle and x_sign have the same sign, A ; will be positive, else it will be negative. So A will ; contain the sign bit of x_sign * flipped alpha sign, ; which is the opposite to the sign of the above result, ; so we now have: ; ; (A P+2 P+1) = - (x_sign x_hi x_lo) * alpha / 256 LDX #3 ; Set (A P+2 P+1) = (y_sign y_hi y_lo) + (A P+2 P+1) JSR MVT6 ; = y - x * alpha / 256 STA K2+3 ; Set K2(3) = A = the sign of the result LDA P+1 ; Set K2(1) = P+1, the low byte of the result STA K2+1 EOR #%11111111 ; Set P = ~K2+1 (i.e. with all its bits flipped) so STA P ; that we can pass K2+1 to MLTU2 below) LDA P+2 ; Set K2(2) = A = P+2 STA K2+2 ; So we now have result 1 above: ; ; K2(3 2 1) = (A P+2 P+1) ; = y - x * alpha / 256 LDX BET1 ; Fetch the magnitude of the current pitch into X, so ; if the pitch angle is beta, X contains |beta| JSR MLTU2-2 ; Set (A P+1 P) = (A ~P) * X ; = K2(2 1) * beta STA P+2 ; Store the high byte of the result in P+2, so we now ; have: ; ; P(2 1 0) = K2(2 1) * beta LDA K2+3 ; Fetch the sign of the above result in K(3 2 1) from EOR BET2 ; K2+3 and EOR with BET2, the sign of the current pitch ; rate, so if the pitch and K(3 2 1) have the same sign, ; A will be positive, else it will be negative. So A ; will contain the sign bit of K(3 2 1) * beta, which is ; the same as the sign of the above result, so we now ; have: ; ; (A P+2 P+1) = K2(3 2 1) * beta / 256 LDX #6 ; Set (A P+2 P+1) = (z_sign z_hi z_lo) + (A P+2 P+1) JSR MVT6 ; = z + K2 * beta / 256 STA INWK+8 ; Set z_sign = A = the sign of the result LDA P+1 ; Set z_lo = P+1, the low byte of the result STA INWK+6 EOR #%11111111 ; Set P = ~z_lo (i.e. with all its bits flipped) so that STA P ; we can pass z_lo to MLTU2 below) LDA P+2 ; Set z_hi = P+2 STA INWK+7 ; So we now have result 2 above: ; ; (z_sign z_hi z_lo) = (A P+2 P+1) ; = z + K2 * beta / 256 JSR MLTU2 ; MLTU2 doesn't change Q, and Q was set to beta in ; the previous call to MLTU2, so this call does: ; ; (A P+1 P) = (A ~P) * Q ; = (z_hi z_lo) * beta STA P+2 ; Set P+2 = A = the high byte of the result, so we ; now have: ; ; P(2 1 0) = (z_hi z_lo) * beta LDA K2+3 ; Set y_sign = K2+3 STA INWK+5 EOR BET2 ; EOR y_sign with BET2, the sign of the current pitch EOR INWK+8 ; rate, and z_sign. If the result is positive jump to BPL MV43 ; MV43, otherwise this means beta * z and y have ; different signs, i.e. P(2 1) and K2(3 2 1) have ; different signs, so we need to add them in order to ; calculate K2(2 1) - P(2 1) LDA P+1 ; Set (y_hi y_lo) = K2(2 1) + P(2 1) ADC K2+1 STA INWK+3 LDA P+2 ADC K2+2 STA INWK+4 JMP MV44 ; Jump to MV44 to continue the calculation .MV43 LDA K2+1 ; Reversing the logic above, we need to subtract P(2 1) SBC P+1 ; and K2(3 2 1) to calculate K2(2 1) - P(2 1), so this STA INWK+3 ; sets (y_hi y_lo) = K2(2 1) - P(2 1) LDA K2+2 SBC P+2 STA INWK+4 BCS MV44 ; If the above subtraction did not underflow, then ; jump to MV44, otherwise we need to negate the result LDA #1 ; Negate (y_sign y_hi y_lo) using two's complement, SBC INWK+3 ; first doing the low bytes: STA INWK+3 ; ; y_lo = 1 - y_lo LDA #0 ; Then the high bytes: SBC INWK+4 ; STA INWK+4 ; y_hi = 0 - y_hi LDA INWK+5 ; And finally flip the sign in y_sign EOR #%10000000 STA INWK+5 .MV44 ; So we now have result 3 above: ; ; (y_sign y_hi y_lo) = K2(2 1) - P(2 1) ; = K2 - beta * z LDX ALP1 ; Fetch the magnitude of the current roll into X, so ; if the roll angle is alpha, X contains |alpha| LDA INWK+3 ; Set P = ~y_lo (i.e. with all its bits flipped) so that EOR #$FF ; we can pass y_lo to MLTU2 below) STA P LDA INWK+4 ; Set A = y_hi JSR MLTU2-2 ; Set (A P+1 P) = (A ~P) * X ; = (y_hi y_lo) * alpha STA P+2 ; Store the high byte of the result in P+2, so we now ; have: ; ; P(2 1 0) = (y_hi y_lo) * alpha LDA ALP2 ; Fetch the correct sign of the current roll angle alpha EOR INWK+5 ; from ALP2 and EOR with byte #5 (y_sign), so if the ; correct roll angle and y_sign have the same sign, A ; will be positive, else it will be negative. So A will ; contain the sign bit of x_sign * correct alpha sign, ; which is the same as the sign of the above result, ; so we now have: ; ; (A P+2 P+1) = (y_sign y_hi y_lo) * alpha / 256 LDX #0 ; Set (A P+2 P+1) = (x_sign x_hi x_lo) + (A P+2 P+1) JSR MVT6 ; = x + y * alpha / 256 STA INWK+2 ; Set x_sign = A = the sign of the result LDA P+2 ; Set x_hi = P+2, the high byte of the result STA INWK+1 LDA P+1 ; Set x_lo = P+1, the low byte of the result STA INWK ; So we now have result 4 above: ; ; x = x + alpha * y ; ; and the rotation of (x, y, z) is done ; ****************************************************************************** ; ; Name: MVEIT (Part 6 of 9) ; Type: Subroutine ; Category: Moving ; Summary: Move current ship: Move the ship in space according to our speed ; Deep dive: A sense of scale ; ; ------------------------------------------------------------------------------ ; ; This routine has multiple stages. This stage does the following: ; ; * Move the ship in space according to our speed (we already moved it ; according to its own speed in part 3). ; ; We do this by subtracting our speed (i.e. the distance we travel in this ; iteration of the loop) from the other ship's z-coordinate. We subtract because ; they appear to be "moving" in the opposite direction to us, and the whole ; MVEIT routine is about moving the other ships rather than us (even though we ; are the one doing the moving). ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; MV45 Rejoin the MVEIT routine after the rotation, tactics and ; scanner code ; ; ****************************************************************************** .MV45 LDA DELTA ; Set R to our speed in DELTA STA R LDA #%10000000 ; Set A to zeroes but with bit 7 set, so that (A R) is ; a 16-bit number containing -R, or -speed LDX #6 ; Set X to the z-axis so the call to MVT1 does this: JSR MVT1 ; ; (z_sign z_hi z_lo) = (z_sign z_hi z_lo) + (A R) ; = (z_sign z_hi z_lo) - speed LDA TYPE ; If the ship type is not the sun (129) then skip the AND #%10000001 ; next instruction, otherwise return from the subroutine CMP #129 ; as we don't need to rotate the sun around its origin. BNE P%+3 ; Having both the AND and the CMP is a little odd, as ; the sun is the only ship type with bits 0 and 7 set, ; so the AND has no effect and could be removed RTS ; Return from the subroutine, as the ship we are moving ; is the sun and doesn't need any of the following ; ****************************************************************************** ; ; Name: MVEIT (Part 7 of 9) ; Type: Subroutine ; Category: Moving ; Summary: Move current ship: Rotate ship's orientation vectors by pitch/roll ; Deep dive: Orientation vectors ; Pitching and rolling ; ; ------------------------------------------------------------------------------ ; ; This routine has multiple stages. This stage does the following: ; ; * Rotate the ship's orientation vectors according to our pitch and roll ; ; As with the previous step, this is all about moving the other ships rather ; than us (even though we are the one doing the moving). So we rotate the ; current ship's orientation vectors (which defines its orientation in space), ; by the angles we are "moving" the rest of the sky through (alpha and beta, our ; roll and pitch), so the ship appears to us to be stationary while we rotate. ; ; ****************************************************************************** LDY #9 ; Apply our pitch and roll rotations to the current JSR MVS4 ; ship's nosev vector LDY #15 ; Apply our pitch and roll rotations to the current JSR MVS4 ; ship's roofv vector LDY #21 ; Apply our pitch and roll rotations to the current JSR MVS4 ; ship's sidev vector ; ****************************************************************************** ; ; Name: MVEIT (Part 8 of 9) ; Type: Subroutine ; Category: Moving ; Summary: Move current ship: Rotate ship about itself by its own pitch/roll ; Deep dive: Orientation vectors ; Pitching and rolling by a fixed angle ; ; ------------------------------------------------------------------------------ ; ; This routine has multiple stages. This stage does the following: ; ; * If the ship we are processing is rolling or pitching itself, rotate it and ; apply damping if required ; ; ****************************************************************************** LDA INWK+30 ; Fetch the ship's pitch counter and extract the sign AND #%10000000 ; into RAT2 STA RAT2 LDA INWK+30 ; Fetch the ship's pitch counter and extract the value AND #%01111111 ; without the sign bit into A BEQ MV8 ; If the pitch counter is 0, then jump to MV8 to skip ; the following, as the ship is not pitching CMP #%01111111 ; If bits 0-6 are set in the pitch counter (i.e. the ; ship's pitch is not damping down), then the C flag ; will be set by this instruction SBC #0 ; Set A = A - 0 - (1 - C), so if we are damping then we ; reduce A by 1, otherwise it is unchanged ORA RAT2 ; Change bit 7 of A to the sign we saved in RAT2, so ; the updated pitch counter in A retains its sign STA INWK+30 ; Store the updated pitch counter in byte #30 LDX #15 ; Rotate (roofv_x, nosev_x) by a small angle (pitch) LDY #9 JSR MVS5 LDX #17 ; Rotate (roofv_y, nosev_y) by a small angle (pitch) LDY #11 JSR MVS5 LDX #19 ; Rotate (roofv_z, nosev_z) by a small angle (pitch) LDY #13 JSR MVS5 .MV8 LDA INWK+29 ; Fetch the ship's roll counter and extract the sign AND #%10000000 ; into RAT2 STA RAT2 LDA INWK+29 ; Fetch the ship's roll counter and extract the value AND #%01111111 ; without the sign bit into A BEQ MV5 ; If the roll counter is 0, then jump to MV5 to skip the ; following, as the ship is not rolling CMP #%01111111 ; If bits 0-6 are set in the roll counter (i.e. the ; ship's roll is not damping down), then the C flag ; will be set by this instruction SBC #0 ; Set A = A - 0 - (1 - C), so if we are damping then we ; reduce A by 1, otherwise it is unchanged ORA RAT2 ; Change bit 7 of A to the sign we saved in RAT2, so ; the updated roll counter in A retains its sign STA INWK+29 ; Store the updated pitch counter in byte #29 LDX #15 ; Rotate (roofv_x, sidev_x) by a small angle (roll) LDY #21 JSR MVS5 LDX #17 ; Rotate (roofv_y, sidev_y) by a small angle (roll) LDY #23 JSR MVS5 LDX #19 ; Rotate (roofv_z, sidev_z) by a small angle (roll) LDY #25 JSR MVS5 ; ****************************************************************************** ; ; Name: MVEIT (Part 9 of 9) ; Type: Subroutine ; Category: Moving ; Summary: Move current ship: Redraw on scanner, if it hasn't been destroyed ; ; ------------------------------------------------------------------------------ ; ; This routine has multiple stages. This stage does the following: ; ; * If the ship is exploding or being removed, hide it on the scanner ; ; * Otherwise redraw the ship on the scanner, now that it's been moved ; ; ****************************************************************************** .MV5 LDA INWK+31 ; Fetch the ship's exploding/killed state from byte #31 AND #%10100000 ; If we are exploding or removing this ship then jump to BNE MVD1 ; MVD1 to remove it from the scanner permanently LDA INWK+31 ; Set bit 4 to keep the ship visible on the scanner ORA #%00010000 STA INWK+31 JMP SCAN ; Display the ship on the scanner, returning from the ; subroutine using a tail call .MVD1 LDA INWK+31 ; Clear bit 4 to hide the ship on the scanner AND #%11101111 STA INWK+31 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MVT1 ; Type: Subroutine ; Category: Moving ; Summary: Calculate (x_sign x_hi x_lo) = (x_sign x_hi x_lo) + (A R) ; ; ------------------------------------------------------------------------------ ; ; Add the signed delta (A R) to a ship's coordinate, along the axis given in X. ; Mathematically speaking, this routine translates the ship along a single axis ; by a signed delta. Taking the example of X = 0, the x-axis, it does the ; following: ; ; (x_sign x_hi x_lo) = (x_sign x_hi x_lo) + (A R) ; ; (In practice, MVT1 is only ever called directly with A = 0 or 128, otherwise ; it is always called via MVT-2, which clears A apart from the sign bit. The ; routine is written to cope with a non-zero delta_hi, so it supports a full ; 16-bit delta, but it appears that delta_hi is only ever used to hold the ; sign of the delta.) ; ; The comments below assume we are adding delta to the x-axis, though the axis ; is determined by the value of X. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; (A R) The signed delta, so A = delta_hi and R = delta_lo ; ; X Determines which coordinate axis of INWK to change: ; ; * X = 0 adds the delta to (x_lo, x_hi, x_sign) ; ; * X = 3 adds the delta to (y_lo, y_hi, y_sign) ; ; * X = 6 adds the delta to (z_lo, z_hi, z_sign) ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; MVT1-2 Clear bits 0-6 of A before entering MVT1 ; ; ****************************************************************************** AND #%10000000 ; Clear bits 0-6 of A .MVT1 ASL A ; Set the C flag to the sign bit of the delta, leaving ; delta_hi << 1 in A STA S ; Set S = delta_hi << 1 ; ; This also clears bit 0 of S LDA #0 ; Set T = just the sign bit of delta (in bit 7) ROR A STA T LSR S ; Set S = delta_hi >> 1 ; = |delta_hi| ; ; This also clear the C flag, as we know that bit 0 of ; S was clear before the LSR EOR INWK+2,X ; If T EOR x_sign has bit 7 set, then x_sign and delta BMI MV10 ; have different signs, so jump to MV10 ; At this point, we know x_sign and delta have the same ; sign, that sign is in T, and S contains |delta_hi|, ; so now we want to do: ; ; (x_sign x_hi x_lo) = (x_sign x_hi x_lo) + (S R) ; ; and then set the sign of the result to the same sign ; as x_sign and delta LDA R ; First we add the low bytes, so: ADC INWK,X ; STA INWK,X ; x_lo = x_lo + R LDA S ; Then we add the high bytes: ADC INWK+1,X ; STA INWK+1,X ; x_hi = x_hi + S LDA INWK+2,X ; And finally we add any carry into x_sign, and if the ADC #0 ; sign of x_sign and delta in T is negative, make sure ORA T ; the result is negative (by OR'ing with T) STA INWK+2,X RTS ; Return from the subroutine .MV10 ; If we get here, we know x_sign and delta have ; different signs, with delta's sign in T, and ; |delta_hi| in S, so now we want to do: ; ; (x_sign x_hi x_lo) = (x_sign x_hi x_lo) - (S R) ; ; and then set the sign of the result according to ; the signs of x_sign and delta LDA INWK,X ; First we subtract the low bytes, so: SEC ; SBC R ; x_lo = x_lo - R STA INWK,X LDA INWK+1,X ; Then we subtract the high bytes: SBC S ; STA INWK+1,X ; x_hi = x_hi - S LDA INWK+2,X ; And finally we subtract any borrow from bits 0-6 of AND #%01111111 ; x_sign, and give the result the opposite sign bit to T SBC #0 ; (i.e. give it the sign of the original x_sign) ORA #%10000000 EOR T STA INWK+2,X BCS MV11 ; If the C flag is set by the above SBC, then our sum ; above didn't underflow and is correct - to put it ; another way, (x_sign x_hi x_lo) >= (S R) so the result ; should indeed have the same sign as x_sign, so jump to ; MV11 to return from the subroutine ; Otherwise our subtraction underflowed because ; (x_sign x_hi x_lo) < (S R), so we now need to flip the ; subtraction around by using two's complement to this: ; ; (S R) - (x_sign x_hi x_lo) ; ; and then we need to give the result the same sign as ; (S R), the delta, as that's the dominant figure in the ; sum LDA #1 ; First we subtract the low bytes, so: SBC INWK,X ; STA INWK,X ; x_lo = 1 - x_lo LDA #0 ; Then we subtract the high bytes: SBC INWK+1,X ; STA INWK+1,X ; x_hi = 0 - x_hi LDA #0 ; And then we subtract the sign bytes: SBC INWK+2,X ; ; x_sign = 0 - x_sign AND #%01111111 ; Finally, we set the sign bit to the sign in T, the ORA T ; sign of the original delta, as the delta is the STA INWK+2,X ; dominant figure in the sum .MV11 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MVS4 ; Type: Subroutine ; Category: Moving ; Summary: Apply pitch and roll to an orientation vector ; Deep dive: Orientation vectors ; Pitching and rolling ; ; ------------------------------------------------------------------------------ ; ; Apply pitch and roll angles alpha and beta to the orientation vector in Y. ; ; Specifically, this routine rotates a point (x, y, z) around the origin by ; pitch alpha and roll beta, using the small angle approximation to make the ; maths easier, and incorporating the Minsky circle algorithm to make the ; rotation more stable (though more elliptic). ; ; If that paragraph makes sense to you, then you should probably be writing ; this commentary! For the rest of us, see the associated deep dives. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y Determines which of the INWK orientation vectors to ; transform: ; ; * Y = 9 rotates nosev: (nosev_x, nosev_y, nosev_z) ; ; * Y = 15 rotates roofv: (roofv_x, roofv_y, roofv_z) ; ; * Y = 21 rotates sidev: (sidev_x, sidev_y, sidev_z) ; ; ****************************************************************************** .MVS4 LDA ALPHA ; Set Q = alpha (the roll angle to rotate through) STA Q LDX INWK+2,Y ; Set (S R) = nosev_y STX R LDX INWK+3,Y STX S LDX INWK,Y ; These instructions have no effect as MAD overwrites STX P ; X and P when called, but they set X = P = nosev_x_lo LDA INWK+1,Y ; Set A = -nosev_x_hi EOR #%10000000 JSR MAD ; Set (A X) = Q * A + (S R) STA INWK+3,Y ; = alpha * -nosev_x_hi + nosev_y STX INWK+2,Y ; ; and store (A X) in nosev_y, so this does: ; ; nosev_y = nosev_y - alpha * nosev_x_hi STX P ; This instruction has no effect as MAD overwrites P, ; but it sets P = nosev_y_lo LDX INWK,Y ; Set (S R) = nosev_x STX R LDX INWK+1,Y STX S LDA INWK+3,Y ; Set A = nosev_y_hi JSR MAD ; Set (A X) = Q * A + (S R) STA INWK+1,Y ; = alpha * nosev_y_hi + nosev_x STX INWK,Y ; ; and store (A X) in nosev_x, so this does: ; ; nosev_x = nosev_x + alpha * nosev_y_hi STX P ; This instruction has no effect as MAD overwrites P, ; but it sets P = nosev_x_lo LDA BETA ; Set Q = beta (the pitch angle to rotate through) STA Q LDX INWK+2,Y ; Set (S R) = nosev_y STX R LDX INWK+3,Y STX S LDX INWK+4,Y STX P ; This instruction has no effect as MAD overwrites P, ; but it sets P = nosev_y LDA INWK+5,Y ; Set A = -nosev_z_hi EOR #%10000000 JSR MAD ; Set (A X) = Q * A + (S R) STA INWK+3,Y ; = beta * -nosev_z_hi + nosev_y STX INWK+2,Y ; ; and store (A X) in nosev_y, so this does: ; ; nosev_y = nosev_y - beta * nosev_z_hi STX P ; This instruction has no effect as MAD overwrites P, ; but it sets P = nosev_y_lo LDX INWK+4,Y ; Set (S R) = nosev_z STX R LDX INWK+5,Y STX S LDA INWK+3,Y ; Set A = nosev_y_hi JSR MAD ; Set (A X) = Q * A + (S R) STA INWK+5,Y ; = beta * nosev_y_hi + nosev_z STX INWK+4,Y ; ; and store (A X) in nosev_z, so this does: ; ; nosev_z = nosev_z + beta * nosev_y_hi RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MVT6 ; Type: Subroutine ; Category: Moving ; Summary: Calculate (A P+2 P+1) = (x_sign x_hi x_lo) + (A P+2 P+1) ; ; ------------------------------------------------------------------------------ ; ; Do the following calculation, for the coordinate given by X (so this is what ; it does for the x-coordinate): ; ; (A P+2 P+1) = (x_sign x_hi x_lo) + (A P+2 P+1) ; ; A is a sign bit and is not included in the calculation, but bits 0-6 of A are ; preserved. Bit 7 is set to the sign of the result. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The sign of P(2 1) in bit 7 ; ; P(2 1) The 16-bit value we want to add the coordinate to ; ; X The coordinate to add, as follows: ; ; * If X = 0, add to (x_sign x_hi x_lo) ; ; * If X = 3, add to (y_sign y_hi y_lo) ; ; * If X = 6, add to (z_sign z_hi z_lo) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A The sign of the result (in bit 7) ; ; ****************************************************************************** .MVT6 TAY ; Store argument A into Y, for later use EOR INWK+2,X ; Set A = A EOR x_sign BMI MV50 ; If the sign is negative, i.e. A and x_sign have ; different signs, jump to MV50 ; The signs are the same, so we can add the two ; arguments and keep the sign to get the result LDA P+1 ; First we add the low bytes: CLC ; ADC INWK,X ; P+1 = P+1 + x_lo STA P+1 LDA P+2 ; And then the high bytes: ADC INWK+1,X ; STA P+2 ; P+2 = P+2 + x_hi TYA ; Restore the original A argument that we stored earlier ; so that we keep the original sign RTS ; Return from the subroutine .MV50 LDA INWK,X ; First we subtract the low bytes: SEC ; SBC P+1 ; P+1 = x_lo - P+1 STA P+1 LDA INWK+1,X ; And then the high bytes: SBC P+2 ; STA P+2 ; P+2 = x_hi - P+2 BCC MV51 ; If the last subtraction underflowed, then the C flag ; will be clear and x_hi < P+2, so jump to MV51 to ; negate the result TYA ; Restore the original A argument that we stored earlier EOR #%10000000 ; but flip bit 7, which flips the sign. We do this ; because x_hi >= P+2 so we want the result to have the ; same sign as x_hi (as it's the dominant side in this ; calculation). The sign of x_hi is x_sign, and x_sign ; has the opposite sign to A, so we flip the sign in A ; to return the correct result RTS ; Return from the subroutine .MV51 LDA #1 ; Our subtraction underflowed, so we negate the result SBC P+1 ; using two's complement, first with the low byte: STA P+1 ; ; P+1 = 1 - P+1 LDA #0 ; And then the high byte: SBC P+2 ; STA P+2 ; P+2 = 0 - P+2 TYA ; Restore the original A argument that we stored earlier ; as this is the correct sign for the result. This is ; because x_hi < P+2, so we want to return the same sign ; as P+2, the dominant side RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MV40 ; Type: Subroutine ; Category: Moving ; Summary: Rotate the planet or sun's location in space by the amount of ; pitch and roll of our ship ; Deep dive: Rotating the universe ; ; ------------------------------------------------------------------------------ ; ; We implement this using the same equations as in part 5 of MVEIT, where we ; rotated the current ship's location by our pitch and roll. Specifically, the ; calculation is as follows: ; ; 1. K2 = y - alpha * x ; 2. z = z + beta * K2 ; 3. y = K2 - beta * z ; 4. x = x + alpha * y ; ; ****************************************************************************** .MV40 LDA ALPHA ; Set Q = -ALPHA, so Q contains the angle we want to EOR #%10000000 ; roll the planet through (i.e. in the opposite STA Q ; direction to our ship's roll angle alpha) LDA INWK ; Set P(1 0) = (x_hi x_lo) STA P LDA INWK+1 STA P+1 LDA INWK+2 ; Set A = x_sign JSR MULT3 ; Set K(3 2 1 0) = (A P+1 P) * Q ; ; which also means: ; ; K(3 2 1) = (A P+1 P) * Q / 256 ; = x * -alpha / 256 ; = - alpha * x / 256 LDX #3 ; Set K(3 2 1) = (y_sign y_hi y_lo) + K(3 2 1) JSR MVT3 ; = y - alpha * x / 256 LDA K+1 ; Set K2(2 1) = P(1 0) = K(2 1) STA K2+1 STA P LDA K+2 ; Set K2+2 = K+2 STA K2+2 STA P+1 ; Set P+1 = K+2 LDA BETA ; Set Q = beta, the pitch angle of our ship STA Q LDA K+3 ; Set K+3 to K2+3, so now we have result 1 above: STA K2+3 ; ; K2(3 2 1) = K(3 2 1) ; = y - alpha * x / 256 ; We also have: ; ; A = K+3 ; ; P(1 0) = K(2 1) ; ; so combined, these mean: ; ; (A P+1 P) = K(3 2 1) ; = K2(3 2 1) JSR MULT3 ; Set K(3 2 1 0) = (A P+1 P) * Q ; ; which also means: ; ; K(3 2 1) = (A P+1 P) * Q / 256 ; = K2(3 2 1) * beta / 256 ; = beta * K2 / 256 LDX #6 ; K(3 2 1) = (z_sign z_hi z_lo) + K(3 2 1) JSR MVT3 ; = z + beta * K2 / 256 LDA K+1 ; Set P = K+1 STA P STA INWK+6 ; Set z_lo = K+1 LDA K+2 ; Set P+1 = K+2 STA P+1 STA INWK+7 ; Set z_hi = K+2 LDA K+3 ; Set A = z_sign = K+3, so now we have: STA INWK+8 ; ; (z_sign z_hi z_lo) = K(3 2 1) ; = z + beta * K2 / 256 ; So we now have result 2 above: ; ; z = z + beta * K2 EOR #%10000000 ; Flip the sign bit of A to give A = -z_sign JSR MULT3 ; Set K(3 2 1 0) = (A P+1 P) * Q ; = (-z_sign z_hi z_lo) * beta ; = -z * beta LDA K+3 ; Set T to the sign bit of K(3 2 1 0), i.e. to the sign AND #%10000000 ; bit of -z * beta STA T EOR K2+3 ; If K2(3 2 1 0) has a different sign to K(3 2 1 0), BMI MV1 ; then EOR'ing them will produce a 1 in bit 7, so jump ; to MV1 to take this into account ; If we get here, K and K2 have the same sign, so we can ; add them together to get the result we're after, and ; then set the sign afterwards LDA K ; We now do the following sum: CLC ; ADC K2 ; (A y_hi y_lo -) = K(3 2 1 0) + K2(3 2 1 0) ; ; starting with the low bytes (which we don't keep) ; ; The CLC has no effect because MULT3 clears the C ; flag, so this instruction could be removed (as it is ; in the cassette version, for example) LDA K+1 ; We then do the middle bytes, which go into y_lo ADC K2+1 STA INWK+3 LDA K+2 ; And then the high bytes, which go into y_hi ADC K2+2 STA INWK+4 LDA K+3 ; And then the sign bytes into A, so overall we have the ADC K2+3 ; following, if we drop the low bytes from the result: ; ; (A y_hi y_lo) = (K + K2) / 256 JMP MV2 ; Jump to MV2 to skip the calculation for when K and K2 ; have different signs .MV1 LDA K ; If we get here then K2 and K have different signs, so SEC ; instead of adding, we need to subtract to get the SBC K2 ; result we want, like this: ; ; (A y_hi y_lo -) = K(3 2 1 0) - K2(3 2 1 0) ; ; starting with the low bytes (which we don't keep) LDA K+1 ; We then do the middle bytes, which go into y_lo SBC K2+1 STA INWK+3 LDA K+2 ; And then the high bytes, which go into y_hi SBC K2+2 STA INWK+4 LDA K2+3 ; Now for the sign bytes, so first we extract the sign AND #%01111111 ; byte from K2 without the sign bit, so P = |K2+3| STA P LDA K+3 ; And then we extract the sign byte from K without the AND #%01111111 ; sign bit, so A = |K+3| SBC P ; And finally we subtract the sign bytes, so P = A - P STA P ; By now we have the following, if we drop the low bytes ; from the result: ; ; (A y_hi y_lo) = (K - K2) / 256 ; ; so now we just need to make sure the sign of the ; result is correct BCS MV2 ; If the C flag is set, then the last subtraction above ; didn't underflow and the result is correct, so jump to ; MV2 as we are done with this particular stage LDA #1 ; Otherwise the subtraction above underflowed, as K2 is SBC INWK+3 ; the dominant part of the subtraction, so we need to STA INWK+3 ; negate the result using two's complement, starting ; with the low bytes: ; ; y_lo = 1 - y_lo LDA #0 ; And then the high bytes: SBC INWK+4 ; STA INWK+4 ; y_hi = 0 - y_hi LDA #0 ; And finally the sign bytes: SBC P ; ; A = 0 - P ORA #%10000000 ; We now force the sign bit to be negative, so that the ; final result below gets the opposite sign to K, which ; we want as K2 is the dominant part of the sum .MV2 EOR T ; T contains the sign bit of K, so if K is negative, ; this flips the sign of A STA INWK+5 ; Store A in y_sign ; So we now have result 3 above: ; ; y = K2 + K ; = K2 - beta * z LDA ALPHA ; Set A = alpha STA Q LDA INWK+3 ; Set P(1 0) = (y_hi y_lo) STA P LDA INWK+4 STA P+1 LDA INWK+5 ; Set A = y_sign JSR MULT3 ; Set K(3 2 1 0) = (A P+1 P) * Q ; = (y_sign y_hi y_lo) * alpha ; = y * alpha LDX #0 ; Set K(3 2 1) = (x_sign x_hi x_lo) + K(3 2 1) JSR MVT3 ; = x + y * alpha / 256 LDA K+1 ; Set (x_sign x_hi x_lo) = K(3 2 1) STA INWK ; = x + y * alpha / 256 LDA K+2 STA INWK+1 LDA K+3 STA INWK+2 ; So we now have result 4 above: ; ; x = x + y * alpha JMP MV45 ; We have now finished rotating the planet or sun by ; our pitch and roll, so jump back into the MVEIT ; routine at MV45 to apply all the other movements ; ****************************************************************************** ; ; Name: Checksum ; Type: Subroutine ; Category: Copy protection ; Summary: Checksum the code from $1000 to $9FFF and check against S%-1 ; ; ------------------------------------------------------------------------------ ; ; This routine is not used in this version of Elite. It is left over from the ; 650s Second Processor version. ; ; ****************************************************************************** .Checksum SEC ; Set the C flag, so it gets included in the checksum LDY #0 ; Set Y = 0, to act as a byte counter STY V ; Set V = 0 LDX #$10 ; Set X = $10, so we start with (X Y) = $1000 LDA (SC),Y ; This has no effect, as A is overwritten by the next ; instruction TXA ; Set A = $10 .CHKLoop STX V+1 ; Set V(1 0) = (X 0) STY T ; Set T = Y ADC (V),Y ; Set A = A + C + contents of (V(1 0) + Y) ; = A + C + contents of ((X 0) + Y) ; = A + C + contents of (X Y) EOR T ; Set A = A EOR Y SBC V+1 ; Set A = A - (1 - C) - X DEY ; Decrement the loop counter to process the next byte BNE CHKLoop ; Loop back until we have done the whole page INX ; Increment the page counter to point to the next page CPX #$A0 ; Loop back to do the next page until X = $A0, when BCC CHKLoop ; (X Y) = $A000 CMP S%-1 ; Compare the calculated checksum in A with the checksum ; stored in S%-1 BNE Checksum ; If the checksum we just calculated does not match ; the value in location S%-1, jump to Checksum to enter ; an infinite loop, which crashes the game RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: PLUT ; Type: Subroutine ; Category: Flight ; Summary: Flip the coordinate axes for the four different views ; Deep dive: Flipping axes between space views ; ; ------------------------------------------------------------------------------ ; ; This routine flips the relevant geometric axes in INWK depending on which ; view we are looking through (front, rear, left, right). ; ; ****************************************************************************** .PLUT LDX VIEW ; Load the current view into X: ; ; 0 = front ; 1 = rear ; 2 = left ; 3 = right BEQ PU2-1 ; If the current view is the front view, return from the ; subroutine (PU2-1 contains an RTS), as the geometry in ; INWK is already correct .PU1 DEX ; Decrement the view, so now: ; ; 0 = rear ; 1 = left ; 2 = right BNE PU2 ; If the current view is left or right, jump to PU2, ; otherwise this is the rear view, so continue on LDA INWK+2 ; Flip the sign of x_sign EOR #%10000000 STA INWK+2 LDA INWK+8 ; Flip the sign of z_sign EOR #%10000000 STA INWK+8 LDA INWK+10 ; Flip the sign of nosev_x_hi EOR #%10000000 STA INWK+10 LDA INWK+14 ; Flip the sign of nosev_z_hi EOR #%10000000 STA INWK+14 LDA INWK+16 ; Flip the sign of roofv_x_hi EOR #%10000000 STA INWK+16 LDA INWK+20 ; Flip the sign of roofv_z_hi EOR #%10000000 STA INWK+20 LDA INWK+22 ; Flip the sign of sidev_x_hi EOR #%10000000 STA INWK+22 LDA INWK+26 ; Flip the sign of roofv_z_hi EOR #%10000000 STA INWK+26 RTS ; Return from the subroutine .PU2 ; We enter this with X set to the view, as follows: ; ; 1 = left ; 2 = right LDA #0 ; Set RAT2 = 0 (left view) or -1 (right view) CPX #2 ROR A STA RAT2 EOR #%10000000 ; Set RAT = -1 (left view) or 0 (right view) STA RAT LDA INWK ; Swap x_lo and z_lo LDX INWK+6 STA INWK+6 STX INWK LDA INWK+1 ; Swap x_hi and z_hi LDX INWK+7 STA INWK+7 STX INWK+1 LDA INWK+2 ; Swap x_sign and z_sign EOR RAT ; If left view, flip sign of new z_sign TAX ; If right view, flip sign of new x_sign LDA INWK+8 EOR RAT2 STA INWK+2 STX INWK+8 LDY #9 ; Swap nosev_x_lo and nosev_z_lo JSR PUS1 ; Swap nosev_x_hi and nosev_z_hi ; If left view, flip sign of new nosev_z_hi ; If right view, flip sign of new nosev_x_hi LDY #15 ; Swap roofv_x_lo and roofv_z_lo JSR PUS1 ; Swap roofv_x_hi and roofv_z_hi ; If left view, flip sign of new roofv_z_hi ; If right view, flip sign of new roofv_x_hi LDY #21 ; Swap sidev_x_lo and sidev_z_lo ; Swap sidev_x_hi and sidev_z_hi ; If left view, flip sign of new sidev_z_hi ; If right view, flip sign of new sidev_x_hi .PUS1 LDA INWK,Y ; Swap the low x and z bytes for the vector in Y: LDX INWK+4,Y ; STA INWK+4,Y ; * For Y = 9 swap nosev_x_lo and nosev_z_lo STX INWK,Y ; * For Y = 15 swap roofv_x_lo and roofv_z_lo ; * For Y = 21 swap sidev_x_lo and sidev_z_lo LDA INWK+1,Y ; Swap the high x and z bytes for the offset in Y: EOR RAT ; TAX ; * If left view, flip sign of new z-coordinate LDA INWK+5,Y ; * If right view, flip sign of new x-coordinate EOR RAT2 STA INWK+1,Y STX INWK+5,Y ; Fall through into LOOK1 to return from the subroutine ; ****************************************************************************** ; ; Name: LOOK1 ; Type: Subroutine ; Category: Flight ; Summary: Initialise the space view ; ; ------------------------------------------------------------------------------ ; ; Initialise the space view, with the direction of view given in X. This clears ; the upper screen and draws the laser crosshairs, if the view in X has lasers ; fitted. It also wipes all the ships from the scanner, so we can recalculate ; ship positions for the new view (they get put back in the main flight loop). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The space view to set: ; ; * 0 = front ; * 1 = rear ; * 2 = left ; * 3 = right ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; LO2 Contains an RTS ; ; ****************************************************************************** .LO2 RTS ; Return from the subroutine .LQ STX VIEW ; Set the current space view to X JSR TT66 ; Clear the top part of the screen, draw a border box, ; and set the current view type in QQ11 to 0 (space ; view) JSR SIGHT ; Draw the laser crosshairs JMP NWSTARS ; Set up a new stardust field and return from the ; subroutine using a tail call .LOOK1 LDA #0 ; Set A = 0, the type number of a space view JSR DOVDU19 ; Switch to the palette for the space view, though this ; doesn't actually do anything in this version of Elite LDY QQ11 ; If the current view is not a space view, jump up to LQ BNE LQ ; to set up a new space view CPX VIEW ; If the current view is already of type X, jump to LO2 BEQ LO2 ; to return from the subroutine (as LO2 contains an RTS) STX VIEW ; Change the current space view to X JSR TT66 ; Clear the top part of the screen, draw a border box, ; and set the current view type in QQ11 to 0 (space ; view) JSR FLIP ; Swap the x- and y-coordinates of all the stardust ; particles and redraw the stardust field JSR WPSHPS ; Wipe all the ships from the scanner and mark them all ; as not being shown on-screen ; And fall through into SIGHT to draw the laser ; crosshairs ; ****************************************************************************** ; ; Name: SIGHT ; Type: Subroutine ; Category: Flight ; Summary: Draw the laser crosshairs ; Deep dive: Sprite usage in Commodore 64 Elite ; ; ****************************************************************************** .SIGHT LDA #%101 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 1 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on ; ; See the memory map at the top of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore LDY VIEW ; Fetch the laser power for our new view LDA LASER,Y BEQ SIG3 ; If it is zero (i.e. there is no laser fitted to this ; view), jump to SIG3 with A = 0 to skip displaying the ; laser sights ; We now set the sprite pointer for sprite 0 so that it ; points to the correct sprite definition for the ; current laser type, so we can display sprite 0 in the ; middle of the space view to show the laser sights LDY #SPOFF% ; The first sprite definition at offset SPOFF% contains ; the sights for the pulse laser, so we start by setting ; Y to the sprite pointer for the first sprite, which is ; for the pulse laser (the sprites are defined in ; elite-sprite.asm) ; The second sprite definition is the beam laser sight, ; the third is the military laser sight and the fourth ; is the mining laser sprite, so we now increment Y (if ; required) to point to the correct sprite definition ; for the current laser type CMP #POW ; If the laser power in A is equal to a pulse laser, BEQ SIG1 ; jump to SIG1 with Y pointing to the first sprite ; definition INY ; Increment Y to point to the beam laser sight in the ; second sprite definition CMP #POW+128 ; If the laser power in A is equal to a beam laser, BEQ SIG1 ; jump to SIG1 with Y pointing to the second sprite ; definition INY ; Increment Y to point to the military laser sight in ; the third sprite definition CMP #Armlas ; If the laser power in A is equal to a military laser, BEQ SIG1 ; jump to SIG1 with Y pointing to the third sprite ; definition INY ; Increment Y to point to the mining laser sight in the ; fourth sprite definition .SIG1 STY $63F8 ; Set the pointer for sprite 0 in the text view to Y ; ; The sprite pointer for sprite 0 is at $63F8 for the ; text view because screen RAM for the text view is ; at $6000 to $63FF, and the sprite pointers always ; live in the last eight bytes of screen RAM, so that's ; from $63F8 to $63FF for sprites 0 to 7 STY $67F8 ; Set the pointer for sprite 0 in the space view to Y ; ; The sprite pointer for sprite 0 is at $67F8 for the ; space view because screen RAM for the space view is ; at $6400 to $67FF, and the sprite pointers always ; live in the last eight bytes of screen RAM, so that's ; from $67F8 to $67FF for sprites 0 to 7 LDA sightcol-SPOFF%,Y ; Fetch the colour from the sightcol table, subtracting ; SPOFF% from Y so we have Y = 0 for pulse lasers ; through to Y = 3 for mining lasers (as we set Y to ; SPOFF% + laser number above) STA VIC+$27 ; Set VIC register $27 to set the colour for sprite 0 to ; the value in A, so the laser sights are shown in the ; correct colour from the sightcol table LDA #1 ; Set A = 1 to store in T to denote that we need to show ; the laser sights .SIG3 STA T ; Store A in T, so T will be 0 if we jumped here without ; wanting to show the laser sights, or 1 if we do want ; to show the laser sights LDA TRIBBLE+1 ; Set A to bits 4-6 of the high byte of TRIBBLE(1 0) AND #%01111111 ; LSR A ; TRIBBLE(1 0) is the number of Trumbles in the hold, LSR A ; and the maximum value of the high byte is $7F, so this LSR A ; sets A to the number of Trumbles in the hold, scaled LSR A ; to the range 0 to %111 (or 0 to 7) ;LSR A ; These instructions are commented out in the original ;ORA #3 ; source TAX ; Copy A into X, so X is now in the range 0 to 7 LDA TRIBTA,X ; Look up the X-th entry in the TRIBTA table, which will ; change X from 7 to 6, but leave X alone otherwise ; (this is a pretty inefficient way to do this, so ; presumably the TRIBTA table approach was used during ; development to fine-tune the relationship between ; Trumble counts and the number of sprites) STA TRIBCT ; Store A in TRIBCT to record the number of Trumble ; sprites to show on-screen, which is in the range 0 to ; 6 (as the six sprites from 2 to 7 are allocated to the ; Trumbles) LDA TRIBMA,X ; The TRIBMA table contains sprite-enable flags for use ; with VIC register $15, where the byte at position X ; contains the correct bits for enabling sprites 2 to 7, ; according to the number of Trumble sprites we want to ; enable, so TRIBMA+2 enables two sprites (2 and 3) for ; example, while TRIBMA+5 enables five sprites (2 to 6) ; ; The Trumble sprites are sprites 2 to 7, so the table ; contains values with bits 0 and 1 clear ; ; So this sets A to the correct sprite-enable mask for ; the set of Trumble sprites that we want to enable ORA T ; T contains 1 if we want to show laser sights or 0 if ; we don't, so this sets bit 0 of A so we show sprite 0 ; if there are laser sights ; ; Both T and the value from TRIBMA have bit 1 clear, so ; this will also disable the explosion sprite if it's ; being shown (which is fine as the explosion sprite is ; only shown fleetingly) STA VIC+$15 ; Set VIC register $15 to enable each of the eight ; sprites, with sprite 0 enabled (bit 0) if there are ; laser sights, and sprites 2 to 7 (bits 2 to 7) enabled ; according to the number of Trumbles in the hold LDA #%100 ; Call SETL1 to set the 6510 input/output port to the JMP SETL1 ; following: ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; and return from the subroutine using a tail call ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore ; ****************************************************************************** ; ; Name: TRIBTA ; Type: Variable ; Category: Missions ; Summary: A table for converting the number of Trumbles in the hold into a ; number of sprites in the range 0 to 6 ; ; ****************************************************************************** .TRIBTA EQUB 0 EQUB 1 EQUB 2 EQUB 3 EQUB 4 EQUB 5 EQUB 6 EQUB 6 ; ****************************************************************************** ; ; Name: TRIBMA ; Type: Variable ; Category: Missions ; Summary: A table for converting the number of Trumbles in the hold into a ; sprite-enable flag to use with VIC register $15 ; ; ****************************************************************************** .TRIBMA EQUB %00000000 ; Disable sprites 2 to 7 EQUB %00000100 ; Enable sprite 2 EQUB %00001100 ; Enable sprites 2 to 3 EQUB %00011100 ; Enable sprites 2 to 4 EQUB %00111100 ; Enable sprites 2 to 5 EQUB %01111100 ; Enable sprites 2 to 6 EQUB %11111100 ; Enable sprites 2 to 7 EQUB %11111100 ; Enable sprites 2 to 7 ; ****************************************************************************** ; ; Name: TT66 ; Type: Subroutine ; Category: Drawing the screen ; Summary: Clear the screen and set the current view type ; ; ------------------------------------------------------------------------------ ; ; Clear the top part of the screen, draw a border box, and set the current ; view type in QQ11 to A. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The type of the new current view (see QQ11 for a list of ; view types) ; ; ****************************************************************************** .TT66 STA QQ11 ; Set the current view type in QQ11 to A ; Fall through into TTX66 to clear the screen and draw a ; border box ; ****************************************************************************** ; ; Name: TTX66 ; Type: Subroutine ; Category: Drawing the screen ; Summary: Clear the top part of the screen, draw a border box and configure ; the specified view ; ; ------------------------------------------------------------------------------ ; ; Clear the top part of the screen (the space view) and draw a border box ; along the top and sides. ; ; ****************************************************************************** .TTX66 JSR MT2 ; Switch to Sentence Case when printing extended tokens ;JSR PBZE ; These instructions are commented out in the original ; ; source ;JSR HBZE LDA #0 ; Reset the ball line heap pointer at LSP ;STA LBUP ; STA LSP ; The STA instruction is commented out in the original ; source LDA #%10000000 ; Set bit 7 of QQ17 to switch to Sentence Case STA QQ17 STA DTW2 ; Set bit 7 of DTW2 to indicate we are not currently ; printing a word JSR FLFLLS ; Call FLFLLS to reset the LSO block ;LDA #YELLOW ; These instructions are commented out in the original ;JSR DOCOL ; source LDA #0 ; Set LAS2 = 0 to stop any laser pulsing STA LAS2 STA DLY ; Set the delay in DLY to 0, to indicate that we are ; no longer showing an in-flight message, so any new ; in-flight messages will be shown instantly STA de ; Clear de, the flag that appends " DESTROYED" to the ; end of the next text token, so that it doesn't LDA #1 ; Move the text cursor to column 1 STA XC STA YC ; Move the text cursor to row 1 JSR TTX66K ; Clear the top part of the screen and draw a yellow ; border LDX QQ22+1 ; Fetch into X the number that's shown on-screen during ; the hyperspace countdown BEQ OLDBOX ; If the counter is zero then we are not counting down ; to hyperspace, so jump to OLDBOX to skip the next ; instruction JSR ee3 ; Print the 8-bit number in X at text location (0, 1), ; i.e. print the hyperspace countdown in the top-left ; corner .OLDBOX LDA #1 ; Move the text cursor to row 1 JSR DOYC LDA QQ11 ; If this is not a space view, jump to tt66 to skip BNE tt66 ; displaying the view name LDA #11 ; Move the text cursor to column 11 JSR DOXC LDA VIEW ; Load the current view into A: ; ; 0 = front ; 1 = rear ; 2 = left ; 3 = right ORA #$60 ; OR with $60 so we get a value of $60 to $63 (96 to 99) JSR TT27 ; Print recursive token 96 to 99, which will be in the ; range "FRONT" to "RIGHT" JSR TT162 ; Print a space LDA #175 ; Print recursive token 15 ("VIEW ") JSR TT27 .tt66 LDX #1 ; Move the text cursor to column 1, row 1 STX XC STX YC DEX ; Set QQ17 = 0 to switch to ALL CAPS STX QQ17 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Save ELTH.bin ; ; ****************************************************************************** PRINT "ELITE H" PRINT "Assembled at ", ~CODE_H% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_H%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_H% PRINT "S.ELTH ", ~CODE_H%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_H% SAVE "3-assembled-output/ELTH.bin", CODE_H%, P%, LOAD% ; ****************************************************************************** ; ; ELITE I FILE ; ; Produces the binary file ELTI.bin that gets loaded by elite-checksum.py. ; ; ****************************************************************************** CODE_I% = P% LOAD_I% = LOAD% + P% - CODE% ; ****************************************************************************** ; ; Name: yetanotherrts ; Type: Subroutine ; Category: Tactics ; Summary: Contains an RTS ; ; ------------------------------------------------------------------------------ ; ; This routine contains an RTS so we can return from the SFRMIS subroutine with ; a branch instruction. ; ; It also contains the DEMON label, which is left over from the 6502 Second ; Processor version, where it implements the demo (there is no demo in this ; version of Elite). ; ; ****************************************************************************** .yetanotherrts .DEMON RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: ECMOF ; Type: Subroutine ; Category: Dashboard ; Summary: Switch off the E.C.M. and turn off the dashboard bulb ; ; ****************************************************************************** .ECMOF LDA #0 ; Set ECMA and ECMP to 0 to indicate that no E.C.M. is STA ECMA ; currently running STA ECMP JSR ECBLB ; Update the E.C.M. indicator bulb on the dashboard LDY #sfxecm ; Call the NOISEOFF routine with A = sfxecm to turn off JMP NOISEOFF ; the sound of the E.C.M. and return from the subroutine ; using a tail call ; ****************************************************************************** ; ; Name: SFRMIS ; Type: Subroutine ; Category: Tactics ; Summary: Add an enemy missile to our local bubble of universe ; ; ------------------------------------------------------------------------------ ; ; An enemy has fired a missile, so add the missile to our universe if there is ; room, and if there is, make the appropriate warnings and noises. ; ; ****************************************************************************** .SFRMIS LDX #MSL ; Set X to the ship type of a missile, and call SFS1-2 JSR SFS1-2 ; to add a missile to our universe that has AI (bit 7 ; set), is hostile (bit 6 set) and has been launched ; (bit 0 clear); the target slot number is set to 31, ; but this is ignored as the hostile flags means we ; are the target BCC yetanotherrts ; The C flag will be set if the call to SFS1-2 was a ; success, so if it's clear, jump to yetanotherrts to ; return from the subroutine (as yetanotherrts contains ; an RTS) LDA #120 ; Print recursive token 120 ("INCOMING MISSILE") as an JSR MESS ; in-flight message LDY #sfxwhosh ; Call the NOISE routine with Y = sfxwhosh to make the JMP NOISE ; sound of the missile being launched and return from ; the subroutine using a tail call ; ****************************************************************************** ; ; Name: EXNO2 ; Type: Subroutine ; Category: Status ; Summary: Process us making a kill ; Deep dive: Combat rank ; ; ------------------------------------------------------------------------------ ; ; We have killed a ship, so increase the kill tally, displaying an iconic ; message of encouragement if the kill total is a multiple of 256, and then ; make a nearby explosion sound. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The type of the ship that was killed ; ; ****************************************************************************** .EXNO2 LDA TALLYL ; We now add the fractional kill count to our tally, CLC ; starting with the fractional bytes: ADC KWL%-1,X ; STA TALLYL ; TALLYL = TALLYL + fractional kill count ; ; where the fractional kill count is taken from the ; KWL% table, according to the ship's type (we look up ; the X-1-th value from KWL% because ship types start ; at 1 rather than 0) LDA TALLY ; And then we add the low byte of TALLY(1 0): ADC KWH%-1,X ; STA TALLY ; TALLY = TALLY + carry + integer kill count ; ; where the integer kill count is taken from the KWH% ; table in the same way BCC davidscockup ; If there is no carry, jump to davidscockup to skip the ; following three instructions, as we have not earned ; a "RIGHT ON COMMANDER!" message INC TALLY+1 ; Increment the high byte of the kill count in TALLY LDA #101 ; The kill total is a multiple of 256, so it's time JSR MESS ; for a pat on the back, so print recursive token 101 ; ("RIGHT ON COMMANDER!") as an in-flight message .davidscockup LDA INWK+7 ; Fetch z_hi, the distance of the ship being hit in ; terms of the z-axis (in and out of the screen) LDX #11 ; We now set X to a number between 11 and 15 depending ; on the z-axis distance to the exploding ship, with 11 ; for distant ships and 15 for close ships CMP #16 ; If z_hi >= 16, jump to quiet2 with X = 11 BCS quiet2 INX ; Increment X to 12 CMP #8 ; If z_hi >= 8, jump to quiet2 with X = 12 BCS quiet2 INX ; Increment X to 13 CMP #6 ; If z_hi >= 6, jump to quiet2 with X = 13 BCS quiet2 INX ; Increment X to 14 CMP #3 ; If z_hi >= 3, jump to quiet2 with X = 14 BCS quiet2 INX ; Increment X to 15 .quiet2 TXA ; Set A = X << 4 ASL A ; ASL A ; So the value of X is in the high nibble of A, so we ASL A ; can pass it to NOISE2 as the sustain volume ASL A ORA #3 ; Set the low nibble of A to 3, so we can pass it to ; NOISE2 as the release length LDY #sfxexpl ; Call the NOISE2 routine with Y = sfxexpl, a frequency LDX #81 ; of 81 in X, and A set according to the explosion JMP NOISE2 ; distance: ; ; * Low nibble of A = release length of 3 ; ; * High nibble of A = sustain volume in the range 11 ; to 15, so closer explosions have a higher sustain ; volume and are therefore louder ; ; The call to NOISE2 returns from the subroutine using a ; tail call ; ****************************************************************************** ; ; Name: EXNO ; Type: Subroutine ; Category: Sound ; Summary: Make the sound of a laser strike on another ship ; ; ------------------------------------------------------------------------------ ; ; Make the two-part explosion sound of us making a laser strike, or of another ; ship exploding. ; ; The volume of the first explosion is affected by the distance of the ship ; being hit, with more distant ships being quieter. ; ; ****************************************************************************** .EXNO LDA INWK+7 ; Fetch z_hi, the distance of the ship being hit in ; terms of the z-axis (in and out of the screen) LDX #11 ; We now set X to a number between 11 and 15 depending ; on the z-axis distance to the exploding ship, with 11 ; for distant ships and 15 for close ships CMP #8 ; If z_hi >= 8, jump to quiet with X = 11 BCS quiet INX ; Increment X to 12 CMP #4 ; If z_hi >= 4, jump to quiet with X = 12 BCS quiet INX ; Increment X to 13 CMP #3 ; If z_hi >= 3, jump to quiet with X = 13 BCS quiet INX ; Increment X to 14 CMP #2 ; If z_hi >= 2, jump to quiet with X = 14 BCS quiet INX ; Increment X to 15 .quiet TXA ; Set A = X << 4 ASL A ; ASL A ; So the value of X is in the high nibble of A ASL A ASL A ORA #3 ; Set the low nibble of A to 3, so we can pass it to ; NOISE2 as the release length LDY #sfxhit ; Call the NOISE2 routine with Y = sfxhit, a frequency LDX #208 ; of 208 in X, and A set according to the explosion JMP NOISE2 ; distance: ; ; * Low nibble of A = release length of 3 ; ; * High nibble of A = sustain volume in the range 11 ; to 15, so closer explosions have a higher sustain ; volume and are therefore louder ; ; The call to NOISE2 returns from the subroutine using a ; tail call ; ****************************************************************************** ; ; Name: BEEP ; Type: Subroutine ; Category: Sound ; Summary: Make a short, high beep ; ; ****************************************************************************** .BEEP LDY #sfxbeep ; Call the NOISE routine with Y = sfxbeep to make a BNE NOISE ; short, high beep, returning from the subroutine using ; a tail call (this BNE is effectively a JMP as Y will ; never be zero) ; ****************************************************************************** ; ; Name: EXNO3 ; Type: Subroutine ; Category: Sound ; Summary: Make an explosion sound ; ; ------------------------------------------------------------------------------ ; ; Make the sound of death in the cold, hard vacuum of space. Apparently, in ; Elite space, everyone can hear you scream. ; ; This routine also makes the sound of a destroyed cargo canister if we don't ; get scooping right, the sound of us colliding with another ship, and the sound ; of us being hit with depleted shields. It is not a good sound to hear. ; ; ****************************************************************************** .EXNO3 LDY #sfxexpl ; Call the NOISE routine with Y = sfxexpl to make the BNE NOISE ; sound of an explosion, returning from the subroutine ; using a tail call (this BNE is effectively a JMP as Y ; will never be zero) ; ****************************************************************************** ; ; Name: SOFLUSH ; Type: Subroutine ; Category: Sound ; Summary: Reset the sound buffers and turn off all sound channels ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; SOUR1 Contains an RTS ; ; ****************************************************************************** .SOFLUSH LDY #3 ; We need to zero the first 3 bytes of the sound buffer ; at SOCNT, so set a counter in Y LDA #1 ; Set A to 1 so we can reset the sound buffer to contain ; values of 1 .SOUL2 STA SOCNT-1,Y ; Zero the Y-1th byte of SOCNT DEY ; Decrement the loop counter BNE SOUL2 ; Loop back to zero the next byte until we have done all ; three from SOFLG+2 down to SOFLG .SOUR1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: NOISEOFF ; Type: Subroutine ; Category: Sound ; Summary: Turn off a specific sound effect in whichever voice it is ; currently playing in ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The number of the sound effect to turn off ; ; ****************************************************************************** .NOISEOFF LDX #3 ; Set X = 3 to use as a counter to work through the ; three voices, so we can match the voice that is ; currently playing the sound effect in Y INY ; Set XX15+2 to the number of the sound effect we want STY XX15+2 ; to turn off, plus 1 .SOUL1 DEX ; Decrement X to work through the voices, so we start ; from 2 and go down to 0 BMI SOUR1 ; If X is negative then we have checked all three ; voices, jump to SOUR1 to return from the subroutine ; (as SOUR1 contains an RTS) LDA SOFLG,X ; Set A to bits 0-5 of SOFLG for voice X AND #%00111111 CMP XX15+2 ; If this doesn't match the incremented sound effect BNE SOUL1 ; number in XX15+2, loop back to check the next voice LDA #1 ; If we get here then voice X is playing the sound STA SOCNT,X ; effect we want to stop, so set the SOCNT entry for ; this voice to 1, to run down the sound's counter and ; stop the sound RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: HYPNOISE ; Type: Subroutine ; Category: Sound ; Summary: Make the sound of the hyperspace drive being engaged ; ; ****************************************************************************** .HYPNOISE LDY #sfxhyp1 ; Call the NOISE2 routine with Y = sfxhyp1, a frequency LDA #$F5 ; of 240 in X, and A set as follows: LDX #240 ; JSR NOISE2 ; * Low nibble of A = release length of 5 ; ; * High nibble of A = sustain volume of 15 LDY #sfxwhosh ; Call the NOISE routine with Y = sfxwhosh to make the JSR NOISE ; sound of the ship launching LDY #1 ; Wait for 1/50 of a second (0.02 seconds) on PAL JSR DELAY ; systems, or 1/60 of a second (0.017 seconds) on NTSC LDY #(sfxhyp1+128) ; Call the NOISE routine with Y = sfxhyp1 + 128, which BNE NOISE ; makes the sfxhyp1 hyperspace effect, but without first ; checking to see if it is already playing (so the ; effect can layer on top of the first sound effect we ; made above) ; ; The call to NOISE returns from the subroutine using a ; tail call (this BNE is effectively a JMP as Y is never ; zero) ; ****************************************************************************** ; ; Name: NOISE2 ; Type: Subroutine ; Category: Sound ; Summary: Make a sound effect with a specific volume and release length ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A Determines the release length and sustain volume of the ; sound effect ; ; * Bits 0-3 contain the release length ; ; * Bits 4-7 contain the sustain volume ; ; X The frequency of the sound effect ; ; ****************************************************************************** .NOISE2 BIT SOUR1 ; SOUR1 contains an RTS instruction, which has opcode ; $60 (or %01100000), and as the BIT instructions sets ; the V flag to bit 6 of its operand, this instruction ; sets the V flag ; ; There is no SEV instruction in the 6502, hence the ; need for this workaround STA XX15 ; Store the sustain volume/release length in XX15 STX XX15+1 ; Store the frequency in XX15+1 EQUB $50 ; Skip the next instruction by turning it into ; $50 $B8, or BVC $B8, which does nothing because we ; set the V flag above ; Fall through into NOISE with the V flag set ; ****************************************************************************** ; ; Name: NOISE ; Type: Subroutine ; Category: Sound ; Summary: Make the sound whose number is in Y ; Deep dive: Sound effects in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The number of the sound effect to be made ; ; If bit 7 is set (i.e. Y = 128 + sound effect number) ; then it will play the sound effect without first ; checking to see if it is already playing, so the sound ; effect can be made on more than one voice at the same ; time ; ; V flag If set, use the values in XX15 and XX15+1 to determine ; the release length, sustain volume and frequency ; ; XX15 Determines the release length and sustain volume of the ; sound effect (when V is set) ; ; * Bits 0-3 contain the release length ; ; * Bits 4-7 contain the sustain volume ; ; XX15+1 The frequency of the sound effect (when V is set) ; ; ****************************************************************************** .NOISE CLV ; Clear the V flag, unless we fell into this routine ; from the NOISE2 routine, in which case this ; instruction is skipped (and V remains set) LDA DNOIZ ; If DNOIZ is non-zero, then sound is disabled, so BNE SOUR1 ; return from the subroutine (as SOUR1 contains an RTS) LDX #2 ; Set X = 2 so we can loop through all three voices, ; from voice 2 down to voice 0 INY ; Set XX15+2 = sound effect number in Y + 1 STY XX15+2 DEY LDA SFXPR,Y ; If bit 0 of SFXPR value for this sound effect is set, LSR A ; then we don't need to check the three voice channels BCS SOUX9 ; to see if any of them are already playing this sound ; effect, so jump to SOUX9 to skip the following ; ; If NOISE was called with a sound effect of 128 + sound ; effect number, this lookup will be fairly random, as ; it is fetching values from game code ; Bit 1 of SFXPR for this sound effect is clear, so now ; we check to see if this sound effect is already ; playing, and if it is, we jump to SOUX6 with the voice ; number in X .SOUX7 LDA SOFLG,X ; Set A to bits 0-5 of SOFLG for voice X, which contains AND #%00111111 ; the sound effect number currently playing in voice X, ; incremented by 1 CMP XX15+2 ; If this matches the incremented sound effect number BEQ SOUX6 ; in XX15+2, then the sound effect is already playing on ; voice X, so jump to SOUX6 to play the new sound effect ; using the same voice DEX ; Decrement the voice number in X BPL SOUX7 ; Loop back to check the next voice, until we have ; checked them all .SOUX9 ; The sound effect is not already being played, so now ; we find out which voice currently has the lowest ; priority, which is stored in the SOPR table LDX #0 ; Set X = 0 to denote voice 1 LDA SOPR ; If SOPR < SOPR+1, jump to SOUX1 as voice 1 currently CMP SOPR+1 ; has a lower priority than voice 2 BCC SOUX1 INX ; Voice 1 has a higher priority than voice 2, so set ; X = 1 to denote voice 2 LDA SOPR+1 ; Set A to the priority of voice 2 .SOUX1 CMP SOPR+2 ; If A < SOPR+2, then the priority in A is a lower BCC P%+4 ; priority than voice 3, so skip the following ; instruction to keep the value of X unchanged LDX #2 ; Set X = 2 so we make the sound effect in voice 3 ; ; So if we jumped here because voice 1 has a lower ; priority than voice 2, we set X as follows: ; ; * X = 0 to choose voice 1 as the lowest priority ; if voice 1 is lower priority than voice 3 ; as v1 < v3 and v1 < v2 ; ; * X = 2 to choose voice 3 as the lowest priority ; if voice 1 is higher priority than voice 3 ; as v1 > v3 and v1 < v2 ; ; If we fell through from above because voice 1 has a ; higher priority than voice 2, then we set X as ; follows: ; ; * X = 1 to choose voice 2 as the lowest priority ; if voice 2 is lower priority than voice 3 ; as v2 < v3 and v1 > v2 ; ; * X = 2 to choose voice 3 as the lowest priority ; if voice 2 is higher priority than voice 3 ; as v2 > v3 and v1 > v2 ; ; The result is that X now contains the voice with the ; lowest priority in the SOPR table, so this is where we ; make our new sound effect .SOUX6 ; By this point X contains the voice number for our new ; sound effect, where X = 0, 1 or 2 (for voices 1 to 3) TYA ; Clear bit 0 of Y, so that if NOISE was called with a AND #%01111111 ; sound effect of 128 + sound effect number, the 128 TAY ; part is now cleared from Y, so Y now contains the ; sound effect number that we want to make LDA SFXPR,Y ; If sound effect Y's priority in SFXPR is less than CMP SOPR,X ; the current priority of voice X in SOPR+X, then the BCC SOUR1 ; sound currently playing in voice X is a higher ; priority than the new sound, so jump to SOUR1 to ; return from the subroutine (as SOUR1 contains an RTS) SEI ; Disable interrupts while we make the sound effect STA SOPR,X ; Store the priority of the sound effect we are making ; in the SOPR entry for voice X, so we can use this to ; check the priority if we want to make sounds in this ; voice in future BVS SOUX4 ; If the V flag is set then we got here via NOISE2, in ; which case the release length and sustain volume were ; passed to the routine in XX15, so skip to SOUX4 to ; set A to this value LDA SFXSUS,Y ; Set A to the release length and sustain volume for ; sound effect Y EQUB $CD ; Skip the next instruction by turning it into ; $CD $A5 $6B, or CMP $6BA5, which does nothing apart ; from affect the C and Z flags ; ; This is similar to the EQUB $2C trick that we see ; throughout Elite, but that uses a BIT opcode to skip ; an instruction, and that would change the value of the ; V flag (which we are using), so here we change the ; next instruction into a CMP instead, as that doesn't ; affect the V flag .SOUX4 LDA XX15 ; Set A to XX15, which contains the release length and ; sustain volume that were passed here via NOISE2 (we ; only run this instruction if the V flag is set) STA SOSUS,X ; Store the release length and sustain volume in A into ; the SOSUS entry for voice X LDA SFXCNT,Y ; Store the counter for sound effect Y in the SOCNT STA SOCNT,X ; entry for voice X LDA SFXFRCH,Y ; Store the frequency change for sound effect Y in the STA SOFRCH,X ; SOFRCH entry for voice X LDA SFXCR,Y ; Store the voice control register for sound effect Y in STA SOCR,X ; the SOCR entry for voice X BVS SOUX5 ; If the V flag is set then we got here via NOISE2, in ; which case the frequency was passed to the routine in ; XX15+1, so skip to SOUX5 to set A to this value LDA SFXFQ,Y ; Set A to the frequency for sound effect Y EQUB $CD ; Skip the next instruction by turning it into ; $CD $A5 $6C, or CMP $6CA5, which does nothing apart ; from affect the C and Z flags .SOUX5 LDA XX15+1 ; Set A to XX15+1, which contains the frequency that was ; passed here via NOISE2 (we only run this instruction ; if the V flag is set) STA SOFRQ,X ; Store the frequency in A into the SOFRQ entry for ; voice X LDA SFXATK,Y ; Store the attack and decay length for sound effect Y STA SOATK,X ; in the SOATK entry for voice X LDA SFXVCH,Y ; Store the volume change rate for sound effect Y in the STA SOVCH,X ; SOVCH entry for voice X INY ; Increment the sound effect number in Y TYA ; Store the incremented sound effect number in the low ORA #%10000000 ; bits of the SOFLG entry for voice X (so the lower bits STA SOFLG,X ; are non-zero) and set bit 7 to indicate that this is a ; new sound effect, so the interrupt handler in SOINT ; can correctly process the sound effect CLI ; Enable interrupts again SEC ; Set the C flag RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: RASTCT ; Type: Variable ; Category: Drawing the screen ; Summary: The current raster count, which flips between 0 and 1 on each call ; to the COMIRQ1 interrupt handler (0 = space view, 1 = dashboard) ; Deep dive: The split-screen mode in Commodore 64 Elite ; ; ****************************************************************************** .RASTCT EQUB 0 ; ****************************************************************************** ; ; Name: zebop ; Type: Variable ; Category: Drawing the screen ; Summary: The value for VIC register $18 to set the screen RAM address for a ; raster count of 0 in the interrupt routine (i.e. the space view) ; Deep dive: The split-screen mode in Commodore 64 Elite ; Colouring the Commodore 64 bitmap screen ; ; ****************************************************************************** .zebop EQUB $81 ; Determines the address of screen RAM to use for colour ; data in the upper portion of the screen (this sets the ; address of screen RAM to $6000 and does not change) ; ****************************************************************************** ; ; Name: abraxas ; Type: Variable ; Category: Drawing the screen ; Summary: The value for VIC register $18 to set the screen RAM address for a ; raster count of 1 in the interrupt routine (i.e. the dashboard) ; Deep dive: The split-screen mode in Commodore 64 Elite ; Colouring the Commodore 64 bitmap screen ; ; ****************************************************************************** .abraxas EQUB $81 ; Determines the address of screen RAM to use for colour ; data the lower portion of the screen, where the ; dashboard lives in the space view: ; ; * When abraxas is $81, the colour of the lower part ; of the screen is determined by screen RAM at $6000 ; (i.e. when the dashboard is not being shown) ; ; * When abraxas is $91, the colour of the lower part ; of the screen is determined by screen RAM at $6400 ; (i.e. when the dashboard is being shown) ; ****************************************************************************** ; ; Name: innersec ; Type: Variable ; Category: Drawing the screen ; Summary: A table for converting the value of X from 0 to 1 or from 1 to 0, ; for use when flipping RASCT between 0 and 1 on each interrupt ; Deep dive: The split-screen mode in Commodore 64 Elite ; ; ****************************************************************************** .innersec EQUB 1 ; Lookup value to change 0 to 1 EQUB 0 ; Lookup value to change 1 to 0 ; ****************************************************************************** ; ; Name: shango ; Type: Variable ; Category: Drawing the screen ; Summary: The raster lines that fire the raster interrupt, so it fires at ; the top of the screen (51) and the top of the dashboard (51 + 143) ; Deep dive: The split-screen mode in Commodore 64 Elite ; ; ****************************************************************************** .shango EQUB 51 + 143 ; The raster line at the top of the dashboard EQUB 51 ; The raster line at the top of the visible screen ; ****************************************************************************** ; ; Name: moonflower ; Type: Variable ; Category: Drawing the screen ; Summary: Controls the energy bomb effect by switching between multicolour ; and standard mode ; Deep dive: The split-screen mode in Commodore 64 Elite ; ; ****************************************************************************** .moonflower EQUB %11000000 ; The bitmap mode for the upper part of the screen ; ****************************************************************************** ; ; Name: caravanserai ; Type: Variable ; Category: Drawing the screen ; Summary: Controls whether multicolour or standard bitmap mode is used for ; the lower part of the screen (i.e. the dashboard) ; Deep dive: The split-screen mode in Commodore 64 Elite ; ; ****************************************************************************** .caravanserai EQUB %11000000 ; The bitmap mode for the lower part of the screen ; ****************************************************************************** ; ; Name: santana ; Type: Variable ; Category: Drawing the screen ; Summary: Controls whether sprite 1 (the explosion sprite) is drawn in ; single colour or multicolour mode ; ; ****************************************************************************** .santana EQUB %11111110 ; Multicolour mode for the upper part of the screen EQUB %11111100 ; Single colour mode for the lower part of the screen ; ****************************************************************************** ; ; Name: lotus ; Type: Variable ; Category: Drawing the screen ; Summary: The colour of the explosion sprite in the upper and lower parts of ; the screen ; Deep dive: The split-screen mode in Commodore 64 Elite ; ; ****************************************************************************** .lotus EQUB 2 ; Colour 2 (red) for %10 bit pairs in the upper part of ; the screen EQUB 0 ; Colour 0 (transparent) for set bits in the lower part ; of the screen ; ****************************************************************************** ; ; Name: welcome ; Type: Variable ; Category: Drawing the screen ; Summary: The background colour for the upper and lower parts of the screen, ; used by the energy bomb to flash the screen's background colour ; Deep dive: The split-screen mode in Commodore 64 Elite ; ; ****************************************************************************** .welcome EQUB 0 ; The background colour for the upper part of the screen EQUB 0 ; The background colour for the lower part of the screen ; ****************************************************************************** ; ; Name: SOUL3b ; Type: Subroutine ; Category: Sound ; Summary: Check whether this is the last voice when making sound effects in ; the interrupt routine, and return from the interrupt if it is ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The voice number that is currently being processed in ; the interrupt routine at SOINT (0 to 2) ; ; ****************************************************************************** .SOUL3b DEY ; Decrement the voice number BPL SOUL8 ; If we have not yet processed all three voices then Y ; will still be positive (i.e. 0, 1 or 2), so jump to ; SOUL8 to process this voice ; If we get here then we have processed all three voices ; in the sound effects interrupt routine, so we now need ; to return from the interrupt PLA ; Retrieve the value of Y we stored on the stack at the TAY ; start of the music section of the interrupt routine, ; so it is preserved ; Fall through into COMIRQ3 to restore the A and X ; registers and the correct memory configuration, and ; return from the interrupt ; ****************************************************************************** ; ; Name: COMIRQ1 ; Type: Subroutine ; Category: Drawing the screen ; Summary: The split screen and sound interrupt handler (the IRQ interrupt ; service hardware vector at $FFFE points here) ; Deep dive: The split-screen mode in Commodore 64 Elite ; Colouring the Commodore 64 bitmap screen ; Sound effects in Commodore 64 Elite ; ; ****************************************************************************** .COMIRQ3 ; If we get here then we want to return from the ; interrupt, so we first have to restore the registers ; we want to preserve, and restore the correct memory ; configuration PLA ; Retrieve the value of X we stored on the stack at the TAX ; start of the interrupt routine, so it is preserved LDA l1 ; Set bits 0 to 2 of the port register at location l1 AND #%11111000 ; ($0001) to bits 0 to 2 of L1M, leaving bits 3 to 7 ORA L1M ; unchanged STA l1 ; ; This sets LORAM, HIRAM and CHAREN to the values in ; L1M, which ensures we return memory to the same ; configuration as when we entered the interrupt routine PLA ; Retrieve the value of A we stored on the stack at the ; start of the interrupt routine, so it is preserved RTI ; Return from the interrupt .COMIRQ1 PHA ; Store A on the stack, so we can preserve it across ; calls to the interrupt handler LDA l1 ; Set bits 0 to 2 of the 6510 port register at location AND #%11111000 ; l1 to %101 to set the input/output port to the ORA #%00000101 ; following: STA l1 ; ; * LORAM = 1 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on ; ; See the memory map at the top of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore .iansint LDA VIC+$19 ; Set bit 7 of VIC register $19, to acknowledge any IRQ ORA #%10000000 ; interrupts that are pending STA VIC+$19 ; ; I'm not sure why we acknowledge IRQ interrupts by ; setting bit 7 rather than setting bit 0 to acknowledge ; the raster interrupt, but perhaps this prevents any ; pending IRQ interrupts from being triggered and ; messing up the timing of the split screen and sound ; interrupt routines TXA ; Store X on the stack, so we can preserve it across PHA ; calls to the interrupt handler LDX RASTCT ; Set X to the current raster count ; ; The code below flips this on each interrupt between ; the two values in innersec, which are set to 0 and 1, ; so X oscillates between 0 and 1 each time the ; interrupt routine is called ; ; The COMIRQ1 interrupt handler is called when the ; raster interrupt is triggered, which happens when the ; VIC-II starts to draw the raster lines defined in the ; shango variable (see below) ; ; These raster lines coincide with the top of the screen ; and the top of the dashboard, so this routine gets ; called twice on each screen redraw, once when the ; raster starts drawing the top of the screen, and again ; when the raster returns to the top of the dashboard ; ; When the interrupt routine is called at the top of the ; screen, the value of RASCT is 0, so the various VIC-II ; registers in the following get set up for the upper ; part of the screen; this includes setting the raster ; interrupt to fire at the top of the dashboard. RASCT ; is flipped to a value of 1 after the upper part of the ; screen has been configured ; ; When the raster reaches the top of the dashboard, the ; interrupt routine is called and RASCT is still 1, so ; the screen is configured for the dashboard (if there ; is one); this includes setting the raster interrupt to ; fire at the top of the screen once again. RASCT is ; flipped back to a value of 0 after the lower part of ; the screen has been configured, ready for the whole ; process to repeat ; ; So, in the following: ; ; * X = RASCT = 0 indicates that we are drawing the ; upper part of the screen ; ; * X = RASCT = 1 indicates that we are drawing the ; lower part of the screen LDA zebop,X ; Set VIC register $18 to the value in zebop (when STA VIC+$18 ; X = 0, for the upper part of the screen) or abraxas ; (when X = 1, for the lower part of the screen) ; ; zebop is always set to $81, which will set the address ; of screen RAM to offset $2000 within the VIC-II bank ; at $4000 (so the screen's colour data is at $6000) ; ; abraxas is $81 by default, in which case this will ; also set screen RAM to $6000, but the wantdials ; routine sets it to $91 when we need to display the ; space view and the dashboard, which sets the address ; of screen RAM to offset $2400 within the VIC-II bank ; at $4000 (so the screen's colour data is at $6400) ; ; In other words: ; ; * When abraxas is $81, the colour of the lower part ; of the screen is determined by screen RAM at $6000 ; (i.e. when the dashboard is not being shown) ; ; * When abraxas is $91, the colour of the lower part ; of the screen is determined by screen RAM at $6400 ; (i.e. when the dashboard is being shown) ; ; This enables us to colour the dashboard independently ; from the corresponding lower part of the text view LDA moonflower,X ; Set VIC register $16 to the value in moonflower (when STA VIC+$16 ; X = 0, for the upper part of the screen) or ; caravanserai (when X = 1, for the lower part of the ; screen) ; ; moonflower has bit 4 clear by default, so this sets ; the upper part of the screen to standard bitmap mode, ; for the space and text views ; ; Bit 4 of moonflower gets set in part 3 of the main ; flight loop when the energy bomb is set off, which ; changes the space view into multicolour bitmap mode ; for the duration of the explosion; this makes the ; space view turn into a coloured mess of double-width ; pixels while the energy bomb is going off ; ; Bit 4 of moonflower gets cleared again in the BOMBOFF ; routine, which is called once the energy bomb has ; finished exploding ; ; caravanserai has bit 4 clear by default, which sets ; the lower part of the screen to standard bitmap mode, ; but the wantdials routine sets bit 4 when we need to ; display the space view and the dashboard, so this ; ensures that the dashboard in the lower part of the ; screen is shown in multicolour bitmap mode LDA shango,X ; Set VIC register $12 to the X-th entry in shango, STA VIC+$12 ; which configures a raster interrupt to fire when the ; VIC-II reaches the relevant line ; ; When X = 0, we are currently configuring the VIC-II ; for the upper part of the screen, so this sets the ; next interrupt to fire at line 51 + 143, which is at ; the top of the dashboard ; ; When X = 1, we are currently configuring the VIC-II ; for the lower part of the screen, this sets the next ; interrupt to fire at line 51, which is at the top of ; the visible screen ; ; So this ensures that the interrupt routine will be ; called once the raster reaches the next line at which ; we need to reconfigure the VIC-II LDA santana,X ; Set VIC register $12 to the X-th entry in santana, STA VIC+$1C ; so it sets bit 1 of the register for the upper part of ; the screen, and clears it again for the lower part ; ; This switches sprite 1, the explosion sprite, between ; multicolour in the upper part of the screen and single ; colour in the bottom part LDA lotus,X ; Set VIC register $28 to the X-th entry in lotus, so STA VIC+$28 ; this sets the colour of sprite 1 to red (colour 2) ; when it's in the upper part of the screen, and to ; colour 0 in the lower part of the screen ; ; As we just switched sprite 1 between multicolour and ; single colour mode (for the upper and lower parts of ; the screen respectively), this means the explosion ; sprite appears in multicolour in the space view (as ; VIC+$28 is used to define the colour of %10 bits in ; the bitmap, so those are shown in red), but in the ; lower part of the screen the sprite is single colour ; with any set bits mapped to colour 0, making the ; sprite transparent ; ; In other words, this restricts the explosion sprite ; to appear in the space view only, so explosions don't ; occur in front of the dashboard BIT BOMB ; If bit 7 of BOMB is zero then the energy bomb is not BPL nobombef ; currently going off, so jump to nobombef to skip the ; following instruction INC welcome ; The energy bomb is going off, so increment welcome so ; we work our way through a range of background colours .nobombef LDA welcome,X ; Set VIC register $21 to the X-th entry in welcome, so STA VIC+$21 ; we change the background colour of the space view ; through a whole range of colours while the energy bomb ; is going off ; ; The value of welcome+1 is never changed, so the colour ; change only applies to the upper part of the screen, ; i.e. the space view ; ; The value of welcome gets set to 0 in the BOMBOFF ; routine, which is called once the energy bomb has ; finished exploding, so this stops the background ; colour from changing LDA innersec,X ; Set RASCT to the X-th entry from innersec, so this STA RASTCT ; flips the value of RASCT from 0 to 1 or from 1 to 0 BNE COMIRQ3 ; If we just flipped RASCT from 0 to 1 then jump to ; COMIRQ3 to return from the interrupt handler ; We now play the background music, if configured ; ; The BNE above means that we only do the following ; every other call to the interrupt handler, which is ; once per frame (so that's 60 or 50 times a second, ; depending on whether this is an NTSC or PAL machine) TYA ; Store Y on the stack, so we can preserve it across PHA ; calls to the interrupt handler BIT MUPLA ; If bit 7 of MUPLA is clear then there is no music BPL SOINT ; currently playing, so jump to SOINT to make any sound ; effects that are in progress JSR BDirqhere ; Play the background music for this frame BIT MUSILLY ; If bit 7 of MUSILLY is set then sounds are configured BMI SOINT ; to be played during music, and we know that music is ; already playing, so jump to SOINT to make any sound ; effects that are in progress JMP coffee ; Otherwise sounds are configured not to play during ; music, and we know that music is playing, so jmp to ; coffee to return from the interrupt handler without ; making the sound effect ; ****************************************************************************** ; ; Name: SOINT ; Type: Subroutine ; Category: Sound ; Summary: Process the contents of the sound buffer and send it to the sound ; chip, to make sound effects as part of the interrupt routine ; Deep dive: Sound effects in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; SOUL8 Process the sound buffer from voice Y to 0 ; ; ****************************************************************************** .SOINT LDY #2 ; We are going to work our way through the three voices ; and process each one in turn, so set a voice counter ; in Y to go from 2 to 0, for voices 3 to 1 .SOUL8 LDA SOFLG,Y ; Set A to the sound flag in SOFLG for voice Y BEQ SOUL3b ; If the sound flag is zero then no sound effect is ; being made on voice Y, so jump to SOUL3b to move on to ; the next voice, or return from the interrupt if this ; is the last voice BMI SOUL4 ; If bit 7 of the sound flag is set then we are just ; starting to make a new sound effect, so jump to SOUL4 ; to reset the SID registers for voice Y LDX SEVENS,Y ; Use the lookup table at SEVENS to set X = 7 * Y, so it ; can be used as an index into the SID registers for ; voice Y (as each of the three voices has seven ; associated register bytes, starting at to SID, SID+$7 ; and SID+$E for voices 1, 2 and 3 respectively) LDA SOFRCH,Y ; If the SOFRCH value for voice Y is zero, then there is BEQ SOUL5 ; no frequency change to apply, so jump to SOUL5 to skip ; past the frequency addition in SOUX2 BNE SOUX2 ; Otherwise jump to SOUX2 to apply the frequency change ; in A (this BNE is effectively a JMP as we just passed ; through a BEQ above) ;EQUB $2C ; This instruction is commented out in the original ; source .SOUL4 ; If we get here then this is a new sound on voice Y, so ; we need to initialise the voice LDA SEVENS,Y ; Use the lookup table at SEVENS to set A = 7 * Y STA SOUX3+1 ; Modify the STA instruction at SOUX3 to point to the ; correct block of seven SID registers for voice Y, so ; we zero the SID registers for voice Y in the following LDA #0 ; Set A = 0 to use for zeroing the SID registers LDX #6 ; There are seven bytes of SID registers for each voice, ; so set a counter in X so we can zero them all .SOUX3 STA SID,X ; Zero SID register X for voice Y ; ; This instruction was modified by the above to point to ; the register block for voice Y, so it zeroes register ; byte X for voice Y, rather than voice 1 DEX ; Decrement the byte counter BPL SOUX3 ; Loop back until we have zeroed all seven bytes of SID ; registers for voice Y LDX SEVENS,Y ; Use the lookup table at SEVENS to set X = 7 * Y, so it ; can be used as an index into the SID registers for ; voice Y LDA SOCR,Y ; Set SID register $4 (the voice control register) for STA SID+$4,X ; voice Y to the value from SOPR for voice Y, to control ; the sound as follows: ; ; * Bit 0: 0 = voice off, release cycle ; 1 = voice on, attack-decay-sustain cycle ; ; * Bit 1 set = synchronization enabled ; ; * Bit 2 set = ring modulation enabled ; ; * Bit 3 set = disable voice, reset noise generator ; ; * Bit 4 set = triangle waveform enabled ; ; * Bit 5 set = saw waveform enabled ; ; * Bit 6 set = square waveform enabled ; ; * Bit 7 set = noise waveform enabled ; ; These values come from the SFXCR table LDA SOATK,Y ; Set SID register $5 (the attack and decay length) for STA SID+$5,X ; voice Y to the value from SOATK for voice Y, to ; control the sound as follows: ; ; * Bits 0-3 = decay length ; ; * Bits 4-7 = attack length ; ; These values come from the SFXATK table LDA SOSUS,Y ; Set SID register $6 (the release length and sustain STA SID+$6,X ; volume) for voice Y to the value from SOSUS for voice ; Y, to control the sound as follows: ; ; * Bits 0-3 = release length ; ; * Bits 4-7 = sustain volume ; ; These values come from the SFXSUS table, but can be ; overridden manually using the NOISE2 routine LDA #0 ; Set A = 0 so the following frequency calculation has ; no effect, as we are just adding 0 to the frequency .SOUX2 ; We jump here if this is an existing sound whose SOFRCH ; value is non-zero, in which case the non-zero value is ; in A ; ; SOFRCH contains a frequency change to be applied in ; each frame, so we now add that to the frequency in ; SOFRQ ; ; If this is a new sound then we get here with A = 0, so ; the frequency doesn't change ; ; Is this is an existing sound with a frequency change ; of 0, then we already jumped past this calculation and ; went straight to SOUL5 from above CLC ; Add A to the SOFRQ value for voice Y, so the frequency CLD ; change gets applied ADC SOFRQ,Y STA SOFRQ,Y PHA ; Store the frequency from SOFRQ on the stack, so we can ; extract the different parts to send to the SID chip LSR A ; Set SID register $1 (high byte of the frequency) for LSR A ; voice Y to bits 2-7 of A STA SID+$1,X PLA ; Set SID register $0 (low byte of the frequency) for ASL A ; voice Y so that bits 0-1 of A are in bits 6-7 ASL A ; ASL A ; So if "f" represents the value from SOFRQ, this sets ASL A ; the 16-bit frequency as follows (with the high byte on ASL A ; the left): ASL A ; STA SID,X ; 00ffffff ff000000 ; ; Or, to put it another way, the frequency is set to ; SOFRQ << 6, or SOFR * 64 LDA PULSEW ; Set SID register $3 (pulse width) for voice Y to the STA SID+$3,X ; value of PULSEW, which oscillates between 2 and 6 ; for each frame .SOUL5 LDA SOFLG,Y ; If bit 7 of the sound flag is set then this is a new BMI SOUL6 ; sound effect, and we just started making it, so jump ; to SOUL6 to clear this bit in the sound flag as this ; is no longer a new sound TYA ; Set X = Y, so both X and Y contain the voice number TAX ; in the range 0 to 2, which we'll call voice Y DEC SOPR,X ; Decrement the priority in SOPR for voice Y, keeping BNE P%+5 ; it above zero, so sounds diminish in priority as they INC SOPR,X ; play out DEC SOCNT,X ; Decrement the counter in SOCNT for voice Y BEQ SOKILL ; If the counter has reached zero then it has just run ; out and this sound effect has finished, so jump to ; SOKILL to terminate it ; The SFXVCH table contains values whose lower bits are ; all set (e.g. %00000011, %00001111, %000111111, ; %11111111 and so on) ; ; If we AND a number this with a value, this the result ; will only be zero when the number is a multiple of the ; SFXVCH value LDA SOCNT,X ; If the sound effect counter is not a multiple of the AND SOVCH,Y ; SOVCH value for voice Y, jump to SOUL3 to move on to BNE SOUL3 ; the next voice ; If we get here then the sound effect counter is a ; multiple of the SOVCH value for voice Y LDA SOSUS,Y ; Subtract 16 from the release length and sustain SEC ; volume in SOSUS for voice Y SBC #16 ; STA SOSUS,Y ; This actually subtracts 1 from the high nibble of the ; release length and sustain volume, and the high nibble ; of the SOSUS value contains the sustain volume, so ; this subtracts 1 from the sustain volume LDX SEVENS,Y ; Use the lookup table at SEVENS to set X = 7 * Y, so it ; can be used as an index into the SID registers for ; voice Y STA SID+$6,X ; Update SID register $6 (release length and sustain ; volume) with the new value to reduce the volume of ; the sound by 1 JMP SOUL3 ; Jump to SOUL3 to move on to the next voice .SOKILL ; If we get here then the sound effect in voice Y has ; reached the end of its counter, so we need to ; terminate it LDX SEVENS,Y ; Use the lookup table at SEVENS to set X = 7 * Y, so it ; can be used as an index into the SID registers for ; voice Y LDA SOCR,Y ; Set SID register $4 (the voice control register) for AND #%11111110 ; voice Y to the value from SOPR for voice Y, but with STA SID+$4,X ; bit 0 clear ; ; Bit 0 controls the sound as follows: ; ; * Bit 0: 0 = voice off, release cycle ; 1 = voice on, attack-decay-sustain cycle ; ; So this turns the voice off, while leaving everything ; else as it was LDA #0 ; Zero the sound flag in SOFLG for voice Y to indicate STA SOFLG,Y ; that no sound effect is playing on this voice any more STA SOPR,Y ; Set the priority in SOPR for voice Y to zero, so any ; new sound effects will always override the priority ; of voice Y BEQ SOUL3 ; Jump to SOUL3 to move on to the next voice (this BEQ ; is effectively a JMP as A is always zero) .SOUL6 ; If we get here then bit 7 of the sound flag is set for ; this sound (to indicate that it's a new sound effect), ; and A contains the whole sound flag AND #%01111111 ; Clear bit 7 of A to indicate that this is no longer a ; new sound effect STA SOFLG,Y ; Update the sound flag for voice Y with the newly ; cleared bit 7 .SOUL3 DEY ; Decrement the voice number in Y to move on to the next ; voice BMI P%+5 ; If we just decremented the voice number to a negative ; value, then we have already processed all three ; voices, so skip the following instruction to return ; from the interrupt handler JMP SOUL8 ; Otherwise Y is still positive, so jump back to SOUL8 ; to make any sound effects on voice Y LDA PULSEW ; Flip bit 2 of PULSEW, so it oscillates between 2 and 6 EOR #%00000100 STA PULSEW ;LDA #1 ; These instructions are commented out in the original ;STA intcnt ; source ; Fall through into coffee to return from the interrupt ; handler ; ****************************************************************************** ; ; Name: coffee ; Type: Subroutine ; Category: Sound ; Summary: Return from the interrupt routine, for when we are making sound ; effects ; ; ****************************************************************************** .coffee PLA ; Retrieve the value of Y we stored on the stack at the TAY ; start of the music section of the interrupt routine, ; so it is preserved PLA ; Retrieve the value of X we stored on the stack at the TAX ; start of the interrupt routine, so it is preserved LDA l1 ; Set bits 0 to 2 of the port register at location l1 AND #%11111000 ; ($0001) to bits 0 to 2 of L1M, leaving bits 3 to 7 ORA L1M ; unchanged STA l1 ; ; This sets LORAM, HIRAM and CHAREN to the values in ; L1M, which ensures we return memory to the same ; configuration as when we entered the interrupt routine PLA ; Retrieve the value of A we stored on the stack at the ; start of the interrupt routine, so it is preserved RTI ; Return from the interrupt ; ****************************************************************************** ; ; Name: Sound variables ; Type: Workspace ; Address: $AA13 to $1461 ; Category: Sound ; Summary: The sound buffer where the data to be sent to the sound chip is ; processed ; Deep dive: Sound effects in Commodore 64 Elite ; ; ****************************************************************************** .SOFLG EQUB 0 ; Sound buffer for sound effect flags EQUB 0 ; EQUB 0 ; SOFLG,Y contains the following: ; ; * Bits 0-5: sound effect number + 1 of the sound ; currently being made on voice Y ; ; * Bit 7 is set if this is a new sound being made, ; rather than one that is in progress .SOCNT EQUB 0 ; Sound buffer for sound effect counters EQUB 0 ; EQUB 0 ; SOCNT,Y contains the counter of the sound currently ; being made on voice Y ; ; The counter decrements each frame, and when it reaches ; zero, the sound effect has finished ; ; These values come from the SFXCNT table .SOPR EQUB 0 ; Sound buffer for sound effect priorities EQUB 0 ; EQUB 0 ; SOPR,Y contains the priority of the sound currently ; being made on voice Y ; ; These values come from the SFXPR table .PULSEW EQUB 2 ; The current pulse width for sound effects ; ; This flips between 2 and 6 on each frame .SOFRCH EQUB 0 ; Sound buffer for frequency change values EQUB 0 ; EQUB 0 ; SOFRCH,Y contains the frequency change to be applied ; to the sound currently being made on voice Y in each ; frame ; ; These values come from the SFXFRCH table .SOFRQ EQUB 0 ; Sound buffer for sound effect frequencies EQUB 0 ; EQUB 0 ; SOFRQ,Y contains the frequency of the sound currently ; being made on voice Y ; ; These values come from the SFXFQ table, and have the ; frequency change from the SFXFRCH table applied in ; each frame ; ; The frequency sent to the SID chip is SOFRQ * 64 .SOCR EQUB 0 ; Sound buffer for voice control register values EQUB 0 ; EQUB 0 ; SOCR,Y contains the voice control register for the ; sound currently being made on voice Y ; ; These values come from the SFXCR table .SOATK EQUB 0 ; Sound buffer for attack and decay lengths EQUB 0 ; EQUB 0 ; SOATK,Y contains the attack and decay length for the ; sound currently being made on voice Y ; ; * Bits 0-3 = decay length ; ; * Bits 4-7 = attack length ; ; These values come from the SFXATK table .SOSUS EQUB 0 ; Sound buffer for release length and sustain volume EQUB 0 ; EQUB 0 ; SOATK,Y contains the release length and sustain volume ; for the sound currently being made on voice Y ; ; * Bits 0-3 = release length ; ; * Bits 4-7 = sustain volume ; ; These values come from the SFXSUS table, but can be ; overridden manually using the NOISE2 routine .SOVCH EQUB 0 ; Sound buffer for the volume change rate EQUB 0 ; EQUB 0 ; SOVCH,Y contains the volume change rate of the sound ; currently being made on voice Y ; ; The sound's volume gets reduced by one every SOVCH,Y ; frames ; ; These values come from the SFXVCH table ; ****************************************************************************** ; ; Name: SEVENS ; Type: Variable ; Category: Sound ; Summary: A table for converting the value of Y to 7 * Y ; ; ****************************************************************************** .SEVENS EQUB 0 ; Lookup value to change 0 to 0 EQUB 7 ; Lookup value to change 1 to 7 EQUB 14 ; Lookup value to change 2 to 14 ; ****************************************************************************** ; ; Name: SFXPR ; Type: Variable ; Category: Sound ; Summary: The priority level for each sound effect ; Deep dive: Sound effects in Commodore 64 Elite ; ; ****************************************************************************** .SFXPR EQUB 114 ; Sound 0 = sfxplas = Pulse lasers fired by us EQUB 112 ; Sound 1 = sfxelas = Being hit by lasers 1 EQUB 116 ; Sound 2 = sfxhit = Other ship exploding EQUB 119 ; Sound 3 = sfxexpl = We died / Collision EQUB 115 ; Sound 4 = sfxwhosh = Missile launched / Ship launch EQUB 104 ; Sound 5 = sfxbeep = Short, high beep EQUB 96 ; Sound 6 = sfxboop = Long, low beep EQUB 240 ; Sound 7 = sfxhyp1 = Hyperspace drive engaged 1 EQUB 48 ; Sound 8 = sfxeng = This sound is not used EQUB 254 ; Sound 9 = sfxecm = E.C.M. on EQUB 114 ; Sound 10 = sfxblas = Beam lasers fired by us EQUB 114 ; Sound 11 = sfxalas = Military lasers fired by us EQUB 146 ; Sound 12 = sfxmlas = Mining lasers fired by us EQUB 225 ; Sound 13 = sfxbomb = Energy bomb EQUB 81 ; Sound 14 = sfxtrib = Trumbles dying EQUB 2 ; Sound 15 = sfxelas2 = Being hit by lasers 2 ; ****************************************************************************** ; ; Name: SFXCNT ; Type: Variable ; Category: Sound ; Summary: The counter for each sound effect, which defines the duration of ; the effect in frames ; Deep dive: Sound effects in Commodore 64 Elite ; ; ****************************************************************************** .SFXCNT EQUB 20 ; Sound 0 = sfxplas = Pulse lasers fired by us EQUB 14 ; Sound 1 = sfxelas = Being hit by lasers 1 EQUB 12 ; Sound 2 = sfxhit = Other ship exploding EQUB 80 ; Sound 3 = sfxexpl = We died / Collision EQUB 63 ; Sound 4 = sfxwhosh = Missile launched / Ship launch EQUB 5 ; Sound 5 = sfxbeep = Short, high beep EQUB 24 ; Sound 6 = sfxboop = Long, low beep EQUB 128 ; Sound 7 = sfxhyp1 = Hyperspace drive engaged 1 EQUB 48 ; Sound 8 = sfxeng = This sound is not used EQUB 255 ; Sound 9 = sfxecm = E.C.M. on EQUB 16 ; Sound 10 = sfxblas = Beam lasers fired by us EQUB 16 ; Sound 11 = sfxalas = Military lasers fired by us EQUB 112 ; Sound 12 = sfxmlas = Mining lasers fired by us EQUB 64 ; Sound 13 = sfxbomb = Energy bomb EQUB 15 ; Sound 14 = sfxtrib = Trumbles dying EQUB 14 ; Sound 15 = sfxelas2 = Being hit by lasers 2 ; ****************************************************************************** ; ; Name: SFXFQ ; Type: Variable ; Category: Sound ; Summary: The frequency (SID+$5) for each sound effect ; Deep dive: Sound effects in Commodore 64 Elite ; ; ****************************************************************************** .SFXFQ EQUB 69 ; Sound 0 = sfxplas = Pulse lasers fired by us EQUB 72 ; Sound 1 = sfxelas = Being hit by lasers 1 EQUB 208 ; Sound 2 = sfxhit = Other ship exploding EQUB 81 ; Sound 3 = sfxexpl = We died / Collision EQUB 64 ; Sound 4 = sfxwhosh = Missile launched / Ship launch EQUB 240 ; Sound 5 = sfxbeep = Short, high beep EQUB 64 ; Sound 6 = sfxboop = Long, low beep EQUB 128 ; Sound 7 = sfxhyp1 = Hyperspace drive engaged 1 EQUB 16 ; Sound 8 = sfxeng = This sound is not used EQUB 80 ; Sound 9 = sfxecm = E.C.M. on EQUB 52 ; Sound 10 = sfxblas = Beam lasers fired by us EQUB 51 ; Sound 11 = sfxalas = Military lasers fired by us EQUB 96 ; Sound 12 = sfxmlas = Mining lasers fired by us EQUB 85 ; Sound 13 = sfxbomb = Energy bomb EQUB 128 ; Sound 14 = sfxtrib = Trumbles dying EQUB 64 ; Sound 15 = sfxelas2 = Being hit by lasers 2 ; ****************************************************************************** ; ; Name: SFXCR ; Type: Variable ; Category: Sound ; Summary: The voice control register (SID+$4) for each sound effect ; Deep dive: Sound effects in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; The voice control register is set in this table as follows: ; ; * Bit 0: 0 = voice off, release cycle ; 1 = voice on, attack-decay-sustain cycle ; ; * Bit 1 set = synchronization enabled ; ; * Bit 2 set = ring modulation enabled ; ; * Bit 3 set = disable voice, reset noise generator ; ; * Bit 4 set = triangle waveform enabled ; ; * Bit 5 set = saw waveform enabled ; ; * Bit 6 set = square waveform enabled ; ; * Bit 7 set = noise waveform enabled ; ; ****************************************************************************** .SFXCR EQUB %01000001 ; Sound 0 = sfxplas = Pulse lasers fired by us EQUB %00010001 ; Sound 1 = sfxelas = Being hit by lasers 1 EQUB %10000001 ; Sound 2 = sfxhit = Other ship exploding EQUB %10000001 ; Sound 3 = sfxexpl = We died / Collision EQUB %10000001 ; Sound 4 = sfxwhosh = Missile launched / Ship launch EQUB %00010001 ; Sound 5 = sfxbeep = Short, high beep EQUB %00010001 ; Sound 6 = sfxboop = Long, low beep EQUB %01000001 ; Sound 7 = sfxhyp1 = Hyperspace drive engaged 1 EQUB %00100001 ; Sound 8 = sfxeng = This sound is not used EQUB %01000001 ; Sound 9 = sfxecm = E.C.M. on EQUB %00100001 ; Sound 10 = sfxblas = Beam lasers fired by us EQUB %00100001 ; Sound 11 = sfxalas = Military lasers fired by us EQUB %00010001 ; Sound 12 = sfxmlas = Mining lasers fired by us EQUB %10000001 ; Sound 13 = sfxbomb = Energy bomb EQUB %00010001 ; Sound 14 = sfxtrib = Trumbles dying EQUB %00100001 ; Sound 15 = sfxelas2 = Being hit by lasers 2 ; ****************************************************************************** ; ; Name: SFXATK ; Type: Variable ; Category: Sound ; Summary: The attack and decay length (SID+$5) for each sound effect ; Deep dive: Sound effects in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; The attack and decay are set in this table as follows: ; ; * Bits 0-3 = decay length ; ; * Bits 4-7 = attack length ; ; ****************************************************************************** .SFXATK EQUB $01 ; Sound 0 = sfxplas = Pulse lasers fired by us EQUB $09 ; Sound 1 = sfxelas = Being hit by lasers 1 EQUB $20 ; Sound 2 = sfxhit = Other ship exploding EQUB $08 ; Sound 3 = sfxexpl = We died / Collision EQUB $0C ; Sound 4 = sfxwhosh = Missile launched / Ship launch EQUB $00 ; Sound 5 = sfxbeep = Short, high beep EQUB $63 ; Sound 6 = sfxboop = Long, low beep EQUB $18 ; Sound 7 = sfxhyp1 = Hyperspace drive engaged 1 EQUB $44 ; Sound 8 = sfxeng = This sound is not used EQUB $11 ; Sound 9 = sfxecm = E.C.M. on EQUB $00 ; Sound 10 = sfxblas = Beam lasers fired by us EQUB $00 ; Sound 11 = sfxalas = Military lasers fired by us EQUB $44 ; Sound 12 = sfxmlas = Mining lasers fired by us EQUB $11 ; Sound 13 = sfxbomb = Energy bomb EQUB $18 ; Sound 14 = sfxtrib = Trumbles dying EQUB $09 ; Sound 15 = sfxelas2 = Being hit by lasers 2 ; ****************************************************************************** ; ; Name: SFXSUS ; Type: Variable ; Category: Sound ; Summary: The release length and sustain volume (SID+$6) for each sound ; effect ; Deep dive: Sound effects in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; The release length and sustain volume are set in this table as follows: ; ; * Bits 0-3 = release length ; ; * Bits 4-7 = sustain volume ; ; ****************************************************************************** .SFXSUS EQUB $D1 ; Sound 0 = sfxplas = Pulse lasers fired by us EQUB $F1 ; Sound 1 = sfxelas = Being hit by lasers 1 EQUB $E5 ; Sound 2 = sfxhit = Other ship exploding EQUB $FB ; Sound 3 = sfxexpl = We died / Collision EQUB $DC ; Sound 4 = sfxwhosh = Missile launched / Ship launch EQUB $F0 ; Sound 5 = sfxbeep = Short, high beep EQUB $F3 ; Sound 6 = sfxboop = Long, low beep EQUB $D8 ; Sound 7 = sfxhyp1 = Hyperspace drive engaged 1 EQUB $00 ; Sound 8 = sfxeng = This sound is not used EQUB $E1 ; Sound 9 = sfxecm = E.C.M. on EQUB $E1 ; Sound 10 = sfxblas = Beam lasers fired by us EQUB $F1 ; Sound 11 = sfxalas = Military lasers fired by us EQUB $F4 ; Sound 12 = sfxmlas = Mining lasers fired by us EQUB $E3 ; Sound 13 = sfxbomb = Energy bomb EQUB $B0 ; Sound 14 = sfxtrib = Trumbles dying EQUB $A1 ; Sound 15 = sfxelas2 = Being hit by lasers 2 ; ****************************************************************************** ; ; Name: SFXFRCH ; Type: Variable ; Category: Sound ; Summary: The frequency change to be applied to each sound effect in each ; frame (as a signed number) ; Deep dive: Sound effects in Commodore 64 Elite ; ; ****************************************************************************** .SFXFRCH EQUB 254 ; Sound 0 = sfxplas = Pulse lasers fired by us EQUB 254 ; Sound 1 = sfxelas = Being hit by lasers 1 EQUB 243 ; Sound 2 = sfxhit = Other ship exploding EQUB 255 ; Sound 3 = sfxexpl = We died / Collision EQUB 0 ; Sound 4 = sfxwhosh = Missile launched / Ship launch EQUB 0 ; Sound 5 = sfxbeep = Short, high beep EQUB 0 ; Sound 6 = sfxboop = Long, low beep EQUB 68 ; Sound 7 = sfxhyp1 = Hyperspace drive engaged 1 EQUB 0 ; Sound 8 = sfxeng = This sound is not used EQUB 85 ; Sound 9 = sfxecm = E.C.M. on EQUB 254 ; Sound 10 = sfxblas = Beam lasers fired by us EQUB 255 ; Sound 11 = sfxalas = Military lasers fired by us EQUB 239 ; Sound 12 = sfxmlas = Mining lasers fired by us EQUB 119 ; Sound 13 = sfxbomb = Energy bomb EQUB 123 ; Sound 14 = sfxtrib = Trumbles dying EQUB 254 ; Sound 15 = sfxelas2 = Being hit by lasers 2 ; ****************************************************************************** ; ; Name: SFXVCH ; Type: Variable ; Category: Sound ; Summary: The volume change rate for each sound effect, i.e. how many frames ; need to pass before the sound effect's volume is reduced by one ; Deep dive: Sound effects in Commodore 64 Elite ; ; ****************************************************************************** .SFXVCH EQUB 3 ; Sound 0 = sfxplas = Pulse lasers fired by us EQUB 3 ; Sound 1 = sfxelas = Being hit by lasers 1 EQUB 3 ; Sound 2 = sfxhit = Other ship exploding EQUB 15 ; Sound 3 = sfxexpl = We died / Collision EQUB 15 ; Sound 4 = sfxwhosh = Missile launched / Ship launch EQUB 255 ; Sound 5 = sfxbeep = Short, high beep EQUB 255 ; Sound 6 = sfxboop = Long, low beep EQUB 31 ; Sound 7 = sfxhyp1 = Hyperspace drive engaged 1 EQUB 255 ; Sound 8 = sfxeng = This sound is not used EQUB 255 ; Sound 9 = sfxecm = E.C.M. on EQUB 3 ; Sound 10 = sfxblas = Beam lasers fired by us EQUB 3 ; Sound 11 = sfxalas = Military lasers fired by us EQUB 15 ; Sound 12 = sfxmlas = Mining lasers fired by us EQUB 255 ; Sound 13 = sfxbomb = Energy bomb EQUB 255 ; Sound 14 = sfxtrib = Trumbles dying EQUB 3 ; Sound 15 = sfxelas2 = Being hit by lasers 2 ; ****************************************************************************** ; ; Name: COLD ; Type: Subroutine ; Category: Loader ; Summary: Configure memory, set up interrupt handlers and configure the ; VIC-II, SID and CIA chips ; Deep dive: The split-screen mode in Commodore 64 Elite ; ; ****************************************************************************** .COLD ; We start by zeroing three pages of memory from $0400 ; to $05FF, so that zeroes the following: ; ; * The UP from workspace $0400 to $0540 ; ; * The WP from workspace $0580 to $06FB ; ; So this initialises all the variables in the UP and WP ; workspaces LDA #4 ; Set the high byte of SC(1 0) to 4 STA SC+1 LDX #3 ; Set X = 3 to act as a page counter, so we zero three ; whole pages of memory LDA #0 ; Set A = 0 so we can use this to zero memory locations STA SC ; Set the low byte of SC(1 0) to zero, so SC is now set ; to $0400 TAY ; Set Y = 0 to act as a byte counter within each page .zerowksploop STA (SC),Y ; Zero the Y-th byte of SC(1 0) INY ; Increment the byte counter BNE zerowksploop ; Loop back until we have zeroed a whole page of memory INC SC+1 ; Increment the high byte of SC(1 0) to point to the ; next page in memory DEX ; Decrement the page counter in X BNE zerowksploop ; Loop back until we have zeroed all three pages LDA #LO(NMIpissoff) ; Set the NMI interrupt vector in NMIV to point to the STA NMIV ; NMIpissoff routine, which acknowledges NMI interrupts LDA #HI(NMIpissoff) ; and ignores them STA NMIV+1 LDA #LO(CHPR2) ; Set the CHRV interrupt vector in CHRV to point to the STA CHRV ; CHPR2 routine, which prints valid ASCII characters LDA #HI(CHPR2) ; using the CHPR routine (so this replaces the normal STA CHRV+1 ; text-printing routine with Elite's own CHPR routine) LDA #%101 ; Call SETL1 to set the 6510 input/output port to the JSR SETL1 ; following: ; ; * LORAM = 1 ; * HIRAM = 0 ; * CHAREN = 1 ; ; This sets the entire 64K memory map to RAM except for ; the I/O memory map at $D000-$DFFF, which gets mapped ; to registers in the VIC-II video controller chip, the ; SID sound chip, the two CIA I/O chips, and so on ; ; See the memory map at the top of page 264 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore SEI ; Disable interrupts while we configure the VIC-II, CIA ; and SID chips and update the interrupt handlers IF NOT(USA%) ; We only include this code if USA% is FALSE ; ; It is designed to slow down PAL machines to match the ; speed of NTSC machines (though the GMA86 PAL version ; doesn't actually include this code) ; ; Specifically, it waits until raster line 256 + PALCK ; is reached before continuing LDA #PALCK ; Set A = PALCK, which contains the bottom byte of the ; the raster line that we want to wait for .UKCHK BIT VIC+$11 ; Loop back to UKCHK until bit 7 of VIC-II register $11 BPL UKCHK ; (control register 1) is set ; ; Bit 7 of register $11 contains the top bit of the ; current raster line (which is a 9-bit value), so this ; waits until the raster has reached at least line 256 CMP VIC+$12 ; Loop back to UKCHK until VIC-II register $12 equals BNE UKCHK ; PALCK ; ; VIC-II register $12 contains the bottom byte of the ; current raster line (which is a 9-bit value), and we ; only get here when the top bit of the raster line is ; set, so this waits until we have reached raster line ; 256 + PALCK ENDIF LDA #%00000011 ; Set CIA1 register $0D to enable and disable interrupts STA CIA+$D ; as follows: ; ; * Bit 0 set = configure interrupts generated by ; timer A underflow ; ; * Bit 1 set = configure interrupts generated by ; timer B underflow ; ; * Bits 2-4 clear = do not change configuration of ; other interrupts ; ; * Bit 7 clear = disable interrupts whose ; corresponding bits are set ; ; So this disables interrupts that are generated by ; timer A underflow and timer B underflow, while leaving ; other interrupts as they are STA CIA2+$D ; Set CIA2 register $0D to enable and disable interrupts ; as follows: ; ; * Bit 0 set = configure interrupts generated by ; timer A underflow ; ; * Bit 1 set = configure interrupts generated by ; timer B underflow ; ; * Bits 2-4 clear = do not change configuration of ; other interrupts ; ; * Bit 7 clear = disable interrupts whose ; corresponding bits are set ; ; So this disables interrupts that are generated by ; timer A underflow and timer B underflow, while leaving ; other interrupts as they are ;LDA #2 ; These instructions are commented out in the original ;STA VIC+$20 ; source LDA #%00001111 ; Set SID register $18 to control the sound as follows: STA SID+$18 ; ; * Bits 0-3: set the volume to 15 (maximum) ; ; * Bit 4 clear: disable the low-pass filter ; ; * Bit 5 clear: disable the bandpass filter ; ; * Bit 6 clear: disable the high-pass filter ; ; * Bit 7 clear: enable voice 3 LDX #0 ; Set the raster count to 0 to initialise the raster STX RASTCT ; effects in the COMIRQ handler (such as the split ; screen) INX ; Set bit 0 of VIC register $1A and clear bits 1-3 to STX VIC+$1A ; configure the following interrupts: ; ; * Bit 0 = enable raster interrupt ; ; * Bit 1 = disable sprite-background collision ; interrupt ; ; * Bit 2 = disable sprite-sprite collision interrupt ; ; * Bit 3 = disable light pen interrupt LDA VIC+$11 ; Clear bit 7 of VIC register $11, to clear the top bit AND #%01111111 ; of the raster line that generates the interrupt (as STA VIC+$11 ; the line number is a 9-bit value, with bits 0-7 in VIC ; register $12) LDA #40 ; Set VIC register $11 to 40, so along with bit 7 of VIC STA VIC+$12 ; register $10, this sets the raster interrupt to be ; generated when the raster reaches line 40 LDA l1 ; Set bits 0 to 2 of the 6510 port register at location AND #%11111000 ; l1 to %100 to set the input/output port to the ORA #%00000100 ; following: STA l1 ; ; * LORAM = 0 ; * HIRAM = 0 ; * CHAREN = 1 ; ; and return from the subroutine using a tail call ; ; This sets the entire 64K memory map to RAM ; ; See the memory map at the top of page 265 in the ; "Commodore 64 Programmer's Reference Guide", published ; by Commodore LDA #%100 ; Set L1M to %100, so the default action of the SETL1 STA L1M ; routine will configure memory as above LDA #LO(NMIpissoff) ; Set the NMI interrupt service hardware vector at $FFFA STA $FFFA ; to point to the NMIpissoff routine, which acknowledges LDA #HI(NMIpissoff) ; NMI interrupts and ignores them STA $FFFB ; ; This ensures that even if the Kernal is not paged into ; memory, NMIs will be processed LDA #HI(COMIRQ1) ; Set the IRQ interrupt service hardware vector at $FFFE STA $FFFF ; to point to COMIRQ1, so it gets called to handle all LDA #LO(COMIRQ1) ; IRQ interrupts and BRK instructions (COMIRQ1 plays the STA $FFFE ; background music and manages the split screen) ; ; This ensures that even if the Kernal is not paged into ; memory, IRQs will be processed CLI ; Re-enable interrupts RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: NMIpissoff ; Type: Subroutine ; Category: Loader ; Summary: Acknowledge NMI interrupts and ignore them ; ; ****************************************************************************** .NMIpissoff CLI ; Enable interrupts, so we acknowledge the NMI and ; basically ignore it RTI ; Return from the interrupt ; ****************************************************************************** ; ; Save ELTI.bin ; ; ****************************************************************************** PRINT "ELITE I" PRINT "Assembled at ", ~CODE_I% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_I%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_I% PRINT "S.ELTI ", ~CODE_I%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_H% SAVE "3-assembled-output/ELTI.bin", CODE_I%, P%, LOAD% ; ****************************************************************************** ; ; ELITE J FILE ; ; Produces the binary file ELTJ.bin that gets loaded by elite-checksum.py. ; ; ****************************************************************************** CODE_J% = P% LOAD_J% = LOAD% + P% - CODE% ; ****************************************************************************** ; ; Name: STARTUP ; Type: Subroutine ; Category: Loader ; Summary: Set the various vectors, interrupts and timers ; ; ------------------------------------------------------------------------------ ; ; This routine is unused in this version of Elite (it is left over from the ; 6502 Second Processor version). ; ; ****************************************************************************** .STARTUP LDA #$FF ; Set COL to $FF, which would set the colour to green STA COL ; for a dashboard indicator (though this code is never ; run) ; Fall through into PUTBACK to return from the ; subroutine ; ****************************************************************************** ; ; Name: PUTBACK ; Type: Subroutine ; Category: Tube ; Summary: Reset the OSWRCH vector in WRCHV to point to USOSWRCH ; ; ------------------------------------------------------------------------------ ; ; This routine is unused in this version of Elite (it is left over from the ; 6502 Second Processor version). ; ; ****************************************************************************** .PUTBACK ;LDA #128 ; This instruction is commented out in the original ; source RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DOHFX ; Type: Subroutine ; Category: Drawing circles ; Summary: Implement the #DOHFX command (update the hyperspace effect ; flag) ; ; ------------------------------------------------------------------------------ ; ; This routine is unused in this version of Elite (it is left over from the ; 6502 Second Processor version). ; ; ****************************************************************************** .DOHFX ;STA HFX ; These instructions are commented out in the original ;JMP PUTBACK ; source ; ****************************************************************************** ; ; Name: DOCOL ; Type: Subroutine ; Category: Text ; Summary: Implement the #SETCOL command (set the current colour) ; ; ------------------------------------------------------------------------------ ; ; This routine is unused in this version of Elite (it is left over from the ; 6502 Second Processor version). ; ; ****************************************************************************** .DOCOL STA COL ; Store the new colour in COL RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: DOSVN ; Type: Subroutine ; Category: Save and load ; Summary: Implement the #DOSVN command (update the "save in progress" ; flag) ; ; ------------------------------------------------------------------------------ ; ; This routine is unused in this version of Elite (it is left over from the ; 6502 Second Processor version). ; ; ****************************************************************************** .DOSVN ;STA svn ; These instructions are commented out in the original ;JMP PUTBACK ; source ; ****************************************************************************** ; ; Name: TWOS ; Type: Variable ; Category: Drawing pixels ; Summary: Ready-made single-pixel character row bytes for the space view ; Deep dive: Drawing pixels in the Commodore 64 version ; ; ------------------------------------------------------------------------------ ; ; Ready-made bytes for plotting one-pixel points in the space view. See the ; PIXEL routine for details. ; ; ****************************************************************************** .TWOS EQUB %10000000 EQUB %01000000 EQUB %00100000 EQUB %00010000 EQUB %00001000 EQUB %00000100 EQUB %00000010 EQUB %00000001 EQUB %10000000 EQUB %01000000 ; ****************************************************************************** ; ; Name: DTWOS ; Type: Variable ; Category: Drawing pixels ; Summary: An unused table of ready-made double-pixel character row bytes ; for the dashboard ; ; ****************************************************************************** .DTWOS EQUB %11000000 EQUB %00110000 EQUB %00001100 EQUB %00000011 EQUD $3060C0C0 ; These bytes appear to be unused; they contain a copy EQUD $03060C18 ; of the TWOS2 variable, and the original source has a ; commented out label .TWOS2 ; ****************************************************************************** ; ; Name: CTWOS2 ; Type: Variable ; Category: Drawing pixels ; Summary: Ready-made single-pixel character row bytes for multicolour bitmap ; mode ; ; ****************************************************************************** .CTWOS2 EQUB %11000000 EQUB %11000000 EQUB %00110000 EQUB %00110000 EQUB %00001100 EQUB %00001100 EQUB %00000011 EQUB %00000011 EQUB %11000000 EQUB %11000000 ; ****************************************************************************** ; ; Name: LIJT1 ; Type: Variable ; Category: Drawing lines ; Summary: Addresses for modifying the low byte of the JMP instruction at ; LI71 to support the unrolled algorithm in part 3 of LOIN ; ; ****************************************************************************** .LIJT1 EQUB LO(LI81) EQUB LO(LI82) EQUB LO(LI83) EQUB LO(LI84) EQUB LO(LI85) EQUB LO(LI86) EQUB LO(LI87) EQUB LO(LI88) ; ****************************************************************************** ; ; Name: LIJT2 ; Type: Variable ; Category: Drawing lines ; Summary: Addresses for modifying the high byte of the JMP instruction at ; LI71 to support the unrolled algorithm in part 3 of LOIN ; ; ****************************************************************************** .LIJT2 EQUB HI(LI81) EQUB HI(LI82) EQUB HI(LI83) EQUB HI(LI84) EQUB HI(LI85) EQUB HI(LI86) EQUB HI(LI87) EQUB HI(LI88) ; ****************************************************************************** ; ; Name: LIJT3 ; Type: Variable ; Category: Drawing lines ; Summary: Addresses for modifying the low byte of the JMP instruction at ; LI72 to support the unrolled algorithm in part 3 of LOIN ; ; ****************************************************************************** .LIJT3 EQUB LO(LI81+6) EQUB LO(LI82+6) EQUB LO(LI83+6) EQUB LO(LI84+6) EQUB LO(LI85+6) EQUB LO(LI86+6) EQUB LO(LI87+6) EQUB LO(LI88+6) ; ****************************************************************************** ; ; Name: LIJT4 ; Type: Variable ; Category: Drawing lines ; Summary: Addresses for modifying the high byte of the JMP instruction at ; LI72 to support the unrolled algorithm in part 3 of LOIN ; ; ****************************************************************************** .LIJT4 EQUB HI(LI81+6) EQUB HI(LI82+6) EQUB HI(LI83+6) EQUB HI(LI84+6) EQUB HI(LI85+6) EQUB HI(LI86+6) EQUB HI(LI87+6) EQUB HI(LI88+6) ; ****************************************************************************** ; ; Name: LIJT5 ; Type: Variable ; Category: Drawing lines ; Summary: Addresses for modifying the low byte of the JMP instruction at ; LI91 to support the unrolled algorithm in part 4 of LOIN ; ; ****************************************************************************** .LIJT5 EQUB LO(LI21) EQUB LO(LI22) EQUB LO(LI23) EQUB LO(LI24) EQUB LO(LI25) EQUB LO(LI26) EQUB LO(LI27) EQUB LO(LI28) ; ****************************************************************************** ; ; Name: LIJT6 ; Type: Variable ; Category: Drawing lines ; Summary: Addresses for modifying the high byte of the JMP instruction at ; LI91 to support the unrolled algorithm in part 4 of LOIN ; ; ****************************************************************************** .LIJT6 EQUB HI(LI21) EQUB HI(LI22) EQUB HI(LI23) EQUB HI(LI24) EQUB HI(LI25) EQUB HI(LI26) EQUB HI(LI27) EQUB HI(LI28) ; ****************************************************************************** ; ; Name: LIJT7 ; Type: Variable ; Category: Drawing lines ; Summary: Addresses for modifying the low byte of the JMP instruction at ; LI92 to support the unrolled algorithm in part 4 of LOIN ; ; ****************************************************************************** .LIJT7 EQUB LO(LI21+6) EQUB LO(LI22+6) EQUB LO(LI23+6) EQUB LO(LI24+6) EQUB LO(LI25+6) EQUB LO(LI26+6) EQUB LO(LI27+6) EQUB LO(LI28+6) ; ****************************************************************************** ; ; Name: LIJT8 ; Type: Variable ; Category: Drawing lines ; Summary: Addresses for modifying the high byte of the JMP instruction at ; LI92 to support the unrolled algorithm in part 4 of LOIN ; ; ****************************************************************************** .LIJT8 EQUB HI(LI21+6) EQUB HI(LI22+6) EQUB HI(LI23+6) EQUB HI(LI24+6) EQUB HI(LI25+6) EQUB HI(LI26+6) EQUB HI(LI27+6) EQUB HI(LI28+6) ; ****************************************************************************** ; ; Name: LOIN (Part 1 of 7) ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a line: Calculate the line gradient in the form of deltas ; Deep dive: Elite's line-drawing algorithm ; ; ------------------------------------------------------------------------------ ; ; This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. ; This stage calculates the line deltas. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X1 The screen x-coordinate of the start of the line ; ; Y1 The screen y-coordinate of the start of the line ; ; X2 The screen x-coordinate of the end of the line ; ; Y2 The screen y-coordinate of the end of the line ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Y Y is preserved ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; LL30 LL30 is a synonym for LOIN and draws a line from ; (X1, Y1) to (X2, Y2) ; ; ****************************************************************************** .LL30 SKIP 0 ; LL30 is a synonym for LOIN ; ; In the BBC Micro cassette and disc versions of Elite, ; LL30 and LOIN are synonyms for the same routine, ; presumably because the two developers each had their ; own line routines to start with, and then chose one of ; them for the final game .LOIN STY YSAV ; Store Y into YSAV, so we can preserve it across the ; call to this subroutine LDA #128 ; Set S2 = 128, which is the starting point for the STA S2 ; slope error (representing half a pixel) ASL A ; Set SWAP = 0, as %10000000 << 1 = 0 STA SWAP LDA X2 ; Set A = X2 - X1 SBC X1 ; = delta_x ; ; This subtraction works as the ASL A above sets the C ; flag BCS LI1 ; If X2 > X1 then A is already positive and we can skip ; the next three instructions EOR #%11111111 ; Negate the result in A by flipping all the bits and ADC #1 ; adding 1, i.e. using two's complement to make it ; positive .LI1 STA P2 ; Store A in P2, so P2 = |X2 - X1|, or |delta_x| SEC ; Set the C flag, ready for the subtraction below LDA Y2 ; Set A = Y2 - Y1 SBC Y1 ; = delta_y ; ; This subtraction works as we either set the C flag ; above, or we skipped that SEC instruction with a BCS BCS LI2 ; If Y2 > Y1 then A is already positive and we can skip ; the next two instructions EOR #%11111111 ; Negate the result in A by flipping all the bits and ADC #1 ; adding 1, i.e. using two's complement to make it ; positive .LI2 STA Q2 ; Store A in Q2, so Q2 = |Y2 - Y1|, or |delta_y| CMP P2 ; If Q2 < P2, jump to STPX to step along the x-axis, as BCC STPX ; the line is closer to being horizontal than vertical JMP STPY ; Otherwise Q2 >= P2 so jump to STPY to step along the ; y-axis, as the line is closer to being vertical than ; horizontal ; ****************************************************************************** ; ; Name: LOIN (Part 2 of 7) ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a line: Line has a shallow gradient, step right along x-axis ; Deep dive: Elite's line-drawing algorithm ; ; ------------------------------------------------------------------------------ ; ; This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. ; If we get here, then: ; ; * |delta_y| < |delta_x| ; ; * The line is closer to being horizontal than vertical ; ; * We are going to step right along the x-axis ; ; * We potentially swap coordinates to make sure X1 < X2 ; ; ****************************************************************************** .STPX LDX X1 ; Set X = X1 CPX X2 ; If X1 < X2, jump down to LI3, as the coordinates are BCC LI3 ; already in the order that we want DEC SWAP ; Otherwise decrement SWAP from 0 to $FF, to denote that ; we are swapping the coordinates around LDA X2 ; Swap the values of X1 and X2 STA X1 STX X2 TAX ; Set X = X1 LDA Y2 ; Swap the values of Y1 and Y2 LDY Y1 STA Y1 STY Y2 .LI3 ; By this point we know the line is horizontal-ish and ; X1 < X2, so we're going from left to right as we go ; from X1 to X2 ; The following section calculates: ; ; Q2 = Q2 / P2 ; = |delta_y| / |delta_x| ; ; using the log tables at logL and log to calculate: ; ; A = log(Q2) - log(P2) ; = log(|delta_y|) - log(|delta_x|) ; ; by first subtracting the low bytes of the logarithms ; from the table at LogL, and then subtracting the high ; bytes from the table at log, before applying the ; antilog to get the result of the division and putting ; it in Q2 LDX Q2 ; Set X = |delta_y| BEQ LIlog7 ; If |delta_y| = 0, jump to LIlog7 to return 0 as the ; result of the division LDA logL,X ; Set A = log(Q2) - log(P2) LDX P2 ; = log(|delta_y|) - log(|delta_x|) SEC ; SBC logL,X ; by first subtracting the low bytes of ; log(Q2) - log(P2) BMI LIlog4 ; If A > 127, jump to LIlog4 LDX Q2 ; And then subtracting the high bytes of LDA log,X ; log(Q2) - log(P2) so now A contains the high byte of LDX P2 ; log(Q2) - log(P2) SBC log,X BCS LIlog5 ; If the subtraction fitted into one byte and didn't ; underflow, then log(Q2) - log(P2) < 256, so we jump to ; LIlog5 to return a result of 255 TAX ; Otherwise we set A to the A-th entry from the antilog LDA antilog,X ; table so the result of the division is now in A JMP LIlog6 ; Jump to LIlog6 to return the result .LIlog5 LDA #255 ; The division is very close to 1, so set A to the BNE LIlog6 ; closest possible answer to 256, i.e. 255, and jump to ; LIlog6 to return the result (this BNE is effectively a ; JMP as A is never zero) .LIlog7 LDA #0 ; The numerator in the division is 0, so set A to 0 and BEQ LIlog6 ; jump to LIlog6 to return the result (this BEQ is ; effectively a JMP as A is always zero) .LIlog4 LDX Q2 ; Subtract the high bytes of log(Q2) - log(P2) so now A LDA log,X ; contains the high byte of log(Q2) - log(P2) LDX P2 SBC log,X BCS LIlog5 ; If the subtraction fitted into one byte and didn't ; underflow, then log(Q2) - log(P2) < 256, so we jump to ; LIlog5 to return a result of 255 TAX ; Otherwise we set A to the A-th entry from the LDA antilogODD,X ; antilogODD so the result of the division is now in A .LIlog6 STA Q2 ; Store the result of the division in Q2, so we have: ; ; Q2 = |delta_y| / |delta_x| CLC ; This instruction has no effect as the value of the C ; flag is overridden by the CPY in the following LDY Y1 ; If Y2 < Y1 then skip the following instruction CPY Y2 BCS P%+5 JMP DOWN ; Y2 >= Y1, so jump to DOWN, as we need to draw the line ; to the right and down ; ****************************************************************************** ; ; Name: LOIN (Part 3 of 7) ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a shallow line going right and up or left and down ; Deep dive: Elite's line-drawing algorithm ; ; ------------------------------------------------------------------------------ ; ; This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. ; If we get here, then: ; ; * The line is going right and up (no swap) or left and down (swap) ; ; * X1 < X2 and Y1 > Y2 ; ; * Draw from (X1, Y1) at bottom left to (X2, Y2) at top right, omitting the ; first pixel ; ; This routine looks complex, but that's because the loop that's used in the ; BBC Micro cassette and disc versions has been unrolled to speed it up. The ; algorithm is unchanged, it's just a lot longer. ; ; ****************************************************************************** LDA X1 ; Each character block contains 8 pixel rows, so to get AND #%11111000 ; the address of the first byte in the character block ; that we need to draw into, as an offset from the start ; of the row, we clear bits 0-2 of the x-coordinate in ; X1 CLC ; The ylookup table lets us look up the 16-bit address ADC ylookupl,Y ; of the start of a character row containing a specific STA SC ; pixel, so this fetches the address for the start of LDA ylookuph,Y ; the character row containing the y-coordinate in Y, ADC #0 ; and adds it to the row offset we just calculated in A STA SC+1 TYA ; Set Y = Y mod 8, which is the pixel row within the AND #7 ; character block at which we want to draw the start of TAY ; our line (as each character block has 8 rows) LDA X1 ; Set X = X1 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide) BIT SWAP ; If SWAP is $FF then we swapped the coordinates above, BMI LI70 ; so jump to LI70 to use the correct addresses LDA LIJT1,X ; Modify the JMP instruction at LI71 to jump to the X-th STA LI71+1 ; unrolled code block below (LI81 through LI88) LDA LIJT2,X ; STA LI71+2 ; This ensures that we start drawing at pixel column X ; within the character block LDX P2 ; Set X = P2 ; = |delta_x| ; ; So we can now use X as the pixel counter .LI71 JMP $8888 ; Jump down to the X-th unrolled code block below ; (i.e. LI81 through LI88) ; ; This instruction is modified by the code above .LI70 LDA LIJT3,X ; Modify the JMP instruction at LI72 to jump to the X-th STA LI72+1 ; unrolled code block below (LI81+6 through LI88+6), LDA LIJT4,X ; skipping the first three instructions so we don't draw STA LI72+2 ; the first pixel ; ; This ensures that we start drawing at pixel column X ; within the character block LDX P2 ; Set X = P2 + 1 INX ; = |delta_x| + 1 ; ; so we can now use X as the pixel counter ; ; We add 1 so we can skip the first pixel plot if the ; line is being drawn with swapped coordinates BEQ LIE1 ; If we have just reached the right end of the line, ; jump to LIE1 to return from the subroutine .LI72 JMP $8888 ; Jump down to the X-th unrolled code block below ; (i.e. LI81+6 through LI88+6) ; ; This instruction is modified by the code above .LIE1 LDY YSAV ; Restore Y from YSAV, so that it's preserved RTS ; Return from the subroutine .LI81 LDA #%10000000 ; Set a mask in A to the first pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE1 ; If we have just reached the right end of the line, ; jump to LIE1 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI82 ; If the addition didn't overflow, jump to LI82 to move ; on to the next pixel to draw DEY ; Otherwise we just overflowed, so decrement Y to move ; to the pixel line above BPL LI82-1 ; If Y is positive we are still within the same ; character block, so skip to LI82-1 to move on to the ; next pixel to draw LDA SC ; Otherwise we need to move up into the character block SBC #$40 ; above, so subtract 320 ($140) from SC(1 0) to move up STA SC ; one pixel line, as there are 320 bytes in each LDA SC+1 ; character row in the screen bitmap SBC #$01 STA SC+1 LDY #7 ; Set the pixel line to the last line in the new ; character block CLC ; Clear the C flag, ready for the addition in the next ; part .LI82 LDA #%01000000 ; Set a mask in A to the second pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE1 ; If we have just reached the right end of the line, ; jump to LIE1 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI83 ; If the addition didn't overflow, jump to LI83 to move ; on to the next pixel to draw DEY ; Otherwise we just overflowed, so decrement Y to move ; to the pixel line above BPL LI83-1 ; If Y is positive we are still within the same ; character block, so skip to LI83-1 to move on to the ; next pixel to draw LDA SC ; Otherwise we need to move up into the character block SBC #$40 ; above, so subtract 320 ($140) from SC(1 0) to move up STA SC ; one pixel line, as there are 320 bytes in each LDA SC+1 ; character row in the screen bitmap SBC #$01 STA SC+1 LDY #7 ; Set the pixel line to the last line in the new ; character block CLC ; Clear the C flag, ready for the addition in the next ; part .LI83 LDA #%00100000 ; Set a mask in A to the third pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE1 ; If we have just reached the right end of the line, ; jump to LIE1 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI84 ; If the addition didn't overflow, jump to LI84 to move ; on to the next pixel to draw DEY ; Otherwise we just overflowed, so decrement Y to move ; to the pixel line above BPL LI84-1 ; If Y is positive we are still within the same ; character block, so skip to LI84-1 to move on to the ; next pixel to draw LDA SC ; Otherwise we need to move up into the character block SBC #$40 ; above, so subtract 320 ($140) from SC(1 0) to move up STA SC ; one pixel line, as there are 320 bytes in each LDA SC+1 ; character row in the screen bitmap SBC #$01 STA SC+1 LDY #7 ; Set the pixel line to the last line in the new ; character block CLC ; Clear the C flag, ready for the addition in the next ; part .LI84 LDA #%00010000 ; Set a mask in A to the fourth pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE1 ; If we have just reached the right end of the line, ; jump to LIE1 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI85 ; If the addition didn't overflow, jump to LI85 to move ; on to the next pixel to draw DEY ; Otherwise we just overflowed, so decrement Y to move ; to the pixel line above BPL LI85-1 ; If Y is positive we are still within the same ; character block, so skip to LI85-1 to move on to the ; next pixel to draw LDA SC ; Otherwise we need to move up into the character block SBC #$40 ; above, so subtract 320 ($140) from SC(1 0) to move up STA SC ; one pixel line, as there are 320 bytes in each LDA SC+1 ; character row in the screen bitmap SBC #$01 STA SC+1 LDY #7 ; Set the pixel line to the last line in the new ; character block CLC ; Clear the C flag, ready for the addition in the next ; part .LI85 LDA #%00001000 ; Set a mask in A to the fifth pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE0S ; If we have just reached the right end of the line, ; jump to LIE0 via LIE0S to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI86 ; If the addition didn't overflow, jump to LI86 to move ; on to the next pixel to draw DEY ; Otherwise we just overflowed, so decrement Y to move ; to the pixel line above BPL LI86-1 ; If Y is positive we are still within the same ; character block, so skip to LI86-1 to move on to the ; next pixel to draw LDA SC ; Otherwise we need to move up into the character block SBC #$40 ; above, so subtract 320 ($140) from SC(1 0) to move up STA SC ; one pixel line, as there are 320 bytes in each LDA SC+1 ; character row in the screen bitmap SBC #$01 STA SC+1 LDY #7 ; Set the pixel line to the last line in the new ; character block CLC ; Clear the C flag, ready for the addition in the next ; part .LI86 LDA #%00000100 ; Set a mask in A to the sixth pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE0 ; If we have just reached the right end of the line, ; jump to LIE0 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI87 ; If the addition didn't overflow, jump to LI87 to move ; on to the next pixel to draw DEY ; Otherwise we just overflowed, so decrement Y to move ; to the pixel line above BPL LI87-1 ; If Y is positive we are still within the same ; character block, so skip to LI87-1 to move on to the ; next pixel to draw LDA SC ; Otherwise we need to move up into the character block SBC #$40 ; above, so subtract 320 ($140) from SC(1 0) to move up STA SC ; one pixel line, as there are 320 bytes in each LDA SC+1 ; character row in the screen bitmap SBC #$01 STA SC+1 LDY #7 ; Set the pixel line to the last line in the new ; character block CLC ; Clear the C flag, ready for the addition in the next ; part .LI87 LDA #%00000010 ; Set a mask in A to the seventh pixel in the ; eight-pixel byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X .LIE0S BEQ LIE0 ; If we have just reached the right end of the line, ; jump to LIE0 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI88 ; If the addition didn't overflow, jump to LI88 to move ; on to the next pixel to draw DEY ; Otherwise we just overflowed, so decrement Y to move ; to the pixel line above BPL LI88-1 ; If Y is positive we are still within the same ; character block, so skip to LI88-1 to move on to the ; next pixel to draw LDA SC ; Otherwise we need to move up into the character block SBC #$40 ; above, so subtract 320 ($140) from SC(1 0) to move up STA SC ; one pixel line, as there are 320 bytes in each LDA SC+1 ; character row in the screen bitmap SBC #$01 STA SC+1 LDY #7 ; Set the pixel line to the last line in the new ; character block CLC ; Clear the C flag, ready for the addition in the next ; part .LI88 LDA #%00000001 ; Set a mask in A to the eighth pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE0 ; If we have just reached the right end of the line, ; jump to LIE0 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI89 ; If the addition didn't overflow, jump to LI89 to move ; on to the next pixel to draw DEY ; Otherwise we just overflowed, so decrement Y to move ; to the pixel line above BPL LI89-1 ; If Y is positive we are still within the same ; character block, so skip to LI89-1 to move on to the ; next pixel to draw LDA SC ; Otherwise we need to move up into the character block SBC #$40 ; above, so subtract 320 ($140) from SC(1 0) to move up STA SC ; one pixel line, as there are 320 bytes in each LDA SC+1 ; character row in the screen bitmap SBC #$01 STA SC+1 LDY #7 ; Set the pixel line to the last line in the new ; character block CLC ; Clear the C flag, ready for the addition in the next ; part .LI89 LDA SC ; Add 8 to SC(1 0), starting with the low byte, so SC ADC #8 ; now points to the next character along to the right STA SC BCS P%+5 ; If the addition just overflowed then skip the next ; instruction as we need to increment the high byte JMP LI81 ; Loop back to draw the next character along to the ; right INC SC+1 ; Increment the high byte of SC(1 0), so SC now points ; to the next character along to the right JMP LI81 ; Loop back to draw the next character along to the ; right .LIE0 LDY YSAV ; Restore Y from YSAV, so that it's preserved RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LOIN (Part 4 of 7) ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a shallow line going right and down or left and up ; Deep dive: Elite's line-drawing algorithm ; ; ------------------------------------------------------------------------------ ; ; This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. ; If we get here, then: ; ; * The line is going right and down (no swap) or left and up (swap) ; ; * X1 < X2 and Y1 <= Y2 ; ; * Draw from (X1, Y1) at top left to (X2, Y2) at bottom right, omitting the ; first pixel ; ; This routine looks complex, but that's because the loop that's used in the ; BBC Micro cassette and disc versions has been unrolled to speed it up. The ; algorithm is unchanged, it's just a lot longer. ; ; ****************************************************************************** .DOWN LDA ylookuph,Y ; Set the top byte of SC(1 0) to the address of the STA SC+1 ; start of the character row to draw in, from the ; ylookup table LDA X1 ; Each character block contains 8 pixel rows, so to get AND #%11111000 ; the address of the first byte in the character block ; that we need to draw into, as an offset from the start ; of the row, we clear bits 0-2 of the x-coordinate in ; X1 ADC ylookupl,Y ; The ylookup table lets us look up the 16-bit address STA SC ; of the start of a character row containing a specific BCC P%+5 ; pixel, so this fetches the address for the start of INC SC+1 ; the character row containing the y-coordinate in Y, ; and adds it to the row offset we just calculated in A CLC ; Calculate SC(1 0) = SC(1 0) - 248 SBC #247 ; STA SC ; This enables us to decrement Y towards zero to work BCS P%+4 ; through the character block - see the next comment for DEC SC+1 ; details TYA ; Set bits 3-7 of Y, which contains the pixel row within AND #%00000111 ; the character, and is therefore in the range 0-7, so EOR #%11111000 ; this does Y = 248 + Y TAY ; ; We therefore have the following: ; ; SC(1 0) + Y = SC(1 0) - 248 + 248 + Y ; = SC(1 0) + Y ; ; so the screen location we poke hasn't changed, but Y ; is now a larger number and SC is smaller. This means ; we can increment Y to move down a line, as per usual, ; but we can test for when it reaches the bottom of the ; character block with a simple BEQ rather than checking ; whether it's reached 8, so this appears to be a code ; optimisation ; ; If it helps, you can think of Y as being a negative ; number that we are incrementing towards zero as we ; move along the line - we just need to alter the value ; of SC so that SC(1 0) + Y points to the right address LDA X1 ; Set X = X1 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide) BIT SWAP ; If SWAP is $FF then we swapped the coordinates above, BMI LI90 ; so jump to LI90 to use the correct addresses LDA LIJT5,X ; Modify the JMP instruction at LI91 to jump to the X-th STA LI91+1 ; unrolled code block below (LI21 through LI28) LDA LIJT6,X ; STA LI91+2 ; This ensures that we start drawing at pixel column X ; within the character block LDX P2 ; Set X = P2 ; = |delta_x| ; ; So we can now use X as the pixel counter BEQ LIE0 ; If we have already reached the right end of the line, ; jump to LIE0 to return from the subroutine .LI91 JMP $8888 ; Jump down to the X-th unrolled code block below ; (i.e. LI21 through LI28) ; ; This instruction is modified by the code above .LI90 LDA LIJT7,X ; Modify the JMP instruction at LI92 to jump to the X-th STA LI92+1 ; unrolled code block below (LI21+6 through LI28+6), LDA LIJT8,X ; skipping the first three instructions so we don't draw STA LI92+2 ; the first pixel ; ; This ensures that we start drawing at pixel column X ; within the character block LDX P2 ; Set X = P2 + 1 INX ; = |delta_x| + 1 ; ; so we can now use X as the pixel counter ; ; We add 1 so we can skip the first pixel plot if the ; line is being drawn with swapped coordinates BEQ LIE0 ; If we have just reached the right end of the line, ; jump to LIE0 to return from the subroutine .LI92 JMP $8888 ; Jump down to the X-th unrolled code block below ; (i.e. LI21+6 through LI28+6) ; ; This instruction is modified by the code above .LIE3 LDY YSAV ; Restore Y from YSAV, so that it's preserved RTS ; Return from the subroutine .LI21 LDA #%10000000 ; Set a mask in A to the first pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE3 ; If we have just reached the right end of the line, ; jump to LIE3 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI22 ; If the addition didn't overflow, jump to LI22 to move ; on to the next pixel to draw INY ; Otherwise we just overflowed, so increment Y to move ; to the pixel line below BNE LI22-1 ; If Y < 0 then we are still within the same character ; block, so skip to LI22-1 to clear the C flag and move ; on to the next pixel to draw LDA SC ; Otherwise we need to move up into the character block ADC #$3F ; below, so add 320 ($140) to SC(1 0) to move down one STA SC ; pixel line, as there are 320 bytes in each character LDA SC+1 ; row in the screen bitmap ADC #1 ; STA SC+1 ; We know the C flag is set as we just passed through a ; BCC, so we only need to add $13F to get the result LDY #248 ; Set the pixel line in Y to the first line in that ; character block (as we subtracted 248 from SC above) CLC ; Clear the C flag, ready for the addition in the next ; part .LI22 LDA #%01000000 ; Set a mask in A to the second pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE3 ; If we have just reached the right end of the line, ; jump to LIE3 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI23 ; If the addition didn't overflow, jump to LI23 to move ; on to the next pixel to draw INY ; Otherwise we just overflowed, so increment Y to move ; to the pixel line below BNE LI23-1 ; If Y < 0 then we are still within the same character ; block, so skip to LI23-1 to clear the C flag and move ; on to the next pixel to draw LDA SC ; Otherwise we need to move up into the character block ADC #$3F ; below, so add 320 ($140) to SC(1 0) to move down one STA SC ; pixel line, as there are 320 bytes in each character LDA SC+1 ; row in the screen bitmap ADC #1 ; STA SC+1 ; We know the C flag is set as we just passed through a ; BCC, so we only need to add $13F to get the result LDY #248 ; Set the pixel line in Y to the first line in that ; character block (as we subtracted 248 from SC above) CLC ; Clear the C flag, ready for the addition in the next ; part .LI23 LDA #%00100000 ; Set a mask in A to the third pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE3 ; If we have just reached the right end of the line, ; jump to LIE3 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI24 ; If the addition didn't overflow, jump to LI24 to move ; on to the next pixel to draw INY ; Otherwise we just overflowed, so increment Y to move ; to the pixel line below BNE LI24-1 ; If Y < 0 then we are still within the same character ; block, so skip to LI24-1 to clear the C flag and move ; on to the next pixel to draw LDA SC ; Otherwise we need to move up into the character block ADC #$3F ; below, so add 320 ($140) to SC(1 0) to move down one STA SC ; pixel line, as there are 320 bytes in each row in the LDA SC+1 ; screen bitmap ADC #1 ; STA SC+1 ; We know the C flag is set as we just passed through a ; BCC, so we only need to add $13F to get the result LDY #248 ; Set the pixel line in Y to the first line in that ; character block (as we subtracted 248 from SC above) CLC ; Clear the C flag, ready for the addition in the next ; part .LI24 LDA #%00010000 ; Set a mask in A to the fourth pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE2S ; If we have just reached the right end of the line, ; jump to LIE2 via LIE2S to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI25 ; If the addition didn't overflow, jump to LI25 to move ; on to the next pixel to draw INY ; Otherwise we just overflowed, so increment Y to move ; to the pixel line below BNE LI25-1 ; If Y < 0 then we are still within the same character ; block, so skip to LI25-1 to clear the C flag and move ; on to the next pixel to draw LDA SC ; Otherwise we need to move up into the character block ADC #$3F ; below, so add 320 ($140) to SC(1 0) to move down one STA SC ; pixel line, as there are 320 bytes in each row in the LDA SC+1 ; screen bitmap ADC #1 ; STA SC+1 ; We know the C flag is set as we just passed through a ; BCC, so we only need to add $13F to get the result LDY #248 ; Set the pixel line in Y to the first line in that ; character block (as we subtracted 248 from SC above) CLC ; Clear the C flag, ready for the addition in the next ; part .LI25 LDA #%00001000 ; Set a mask in A to the fifth pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE2S ; If we have just reached the right end of the line, ; jump to LIE2 via LIE2S to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI26 ; If the addition didn't overflow, jump to LI26 to move ; on to the next pixel to draw INY ; Otherwise we just overflowed, so increment Y to move ; to the pixel line below BNE LI26-1 ; If Y < 0 then we are still within the same character ; block, so skip to LI26-1 to clear the C flag and move ; on to the next pixel to draw LDA SC ; Otherwise we need to move up into the character block ADC #$3F ; below, so add 320 ($140) to SC(1 0) to move down one STA SC ; pixel line, as there are 320 bytes in each character LDA SC+1 ; row in the screen bitmap ADC #1 ; STA SC+1 ; We know the C flag is set as we just passed through a ; BCC, so we only need to add $13F to get the result LDY #248 ; Set the pixel line in Y to the first line in that ; character block (as we subtracted 248 from SC above) CLC ; Clear the C flag, ready for the addition in the next ; part .LI26 LDA #%00000100 ; Set a mask in A to the sixth pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE2 ; If we have just reached the right end of the line, ; jump to LIE2 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI27 ; If the addition didn't overflow, jump to LI27 to move ; on to the next pixel to draw INY ; Otherwise we just overflowed, so increment Y to move ; to the pixel line below BNE LI27-1 ; If Y < 0 then we are still within the same character ; block, so skip to LI27-1 to clear the C flag and move ; on to the next pixel to draw LDA SC ; Otherwise we need to move up into the character block ADC #$3F ; below, so add 320 ($140) to SC(1 0) to move down one STA SC ; pixel line, as there are 320 bytes in each character LDA SC+1 ; row in the screen bitmap ADC #1 ; STA SC+1 ; We know the C flag is set as we just passed through a ; BCC, so we only need to add $13F to get the result LDY #248 ; Set the pixel line in Y to the first line in that ; character block (as we subtracted 248 from SC above) CLC ; Clear the C flag, ready for the addition in the next ; part .LI27 LDA #%00000010 ; Set a mask in A to the seventh pixel in the ; eight-pixel EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X .LIE2S BEQ LIE2 ; If we have just reached the right end of the line, ; jump to LIE2 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI28 ; If the addition didn't overflow, jump to LI28 to move ; on to the next pixel to draw INY ; Otherwise we just overflowed, so increment Y to move ; to the pixel line below BNE LI28-1 ; If Y < 0 then we are still within the same character ; block, so skip to LI28-1 to clear the C flag and move ; on to the next pixel to draw LDA SC ; Otherwise we need to move up into the character block ADC #$3F ; below, so add 320 ($140) to SC(1 0) to move down one STA SC ; pixel line, as there are 320 bytes in each character LDA SC+1 ; row in the screen bitmap ADC #1 ; STA SC+1 ; We know the C flag is set as we just passed through a ; BCC, so we only need to add $13F to get the result LDY #248 ; Set the pixel line in Y to the first line in that ; character block (as we subtracted 248 from SC above) CLC ; Clear the C flag, ready for the addition in the next ; part .LI28 LDA #%00000001 ; Set a mask in A to the eighth pixel in the eight-pixel ; byte EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEX ; Decrement the counter in X BEQ LIE2 ; If we have just reached the right end of the line, ; jump to LIE2 to return from the subroutine LDA S2 ; Set S2 = S2 + Q2 to update the slope error ADC Q2 STA S2 BCC LI29 ; If the addition didn't overflow, jump to LI29 to move ; on to the next pixel to draw INY ; Otherwise we just overflowed, so increment Y to move ; to the pixel line below BNE LI29-1 ; If Y < 0 then we are still within the same character ; block, so skip to LI29-1 to clear the C flag and move ; on to the next pixel to draw LDA SC ; Otherwise we need to move up into the character block ADC #$3F ; below, so add 320 ($140) to SC(1 0) to move down one STA SC ; pixel line, as there are 320 bytes in each character LDA SC+1 ; row in the screen bitmap ADC #1 ; STA SC+1 ; We know the C flag is set as we just passed through a ; BCC, so we only need to add $13F to get the result LDY #248 ; Set the pixel line in Y to the first line in that ; character block (as we subtracted 248 from SC above) CLC ; Clear the C flag, ready for the addition in the next ; part .LI29 LDA SC ; Add 8 to SC(1 0), starting with the low byte, so SC ADC #8 ; now points to the next character along to the right STA SC BCC P%+4 ; If the addition didn't overflow, skip the following ; instruction INC SC+1 ; Increment the high byte of SC(1 0), so SC now points ; to the next character along to the right JMP LI21 ; Loop back to draw the next character along to the ; right .LIE2 LDY YSAV ; Restore Y from YSAV, so that it's preserved RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LOIN (Part 5 of 7) ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a line: Line has a steep gradient, step up along y-axis ; Deep dive: Elite's line-drawing algorithm ; ; ------------------------------------------------------------------------------ ; ; This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. ; If we get here, then: ; ; * |delta_y| >= |delta_x| ; ; * The line is closer to being vertical than horizontal ; ; * We are going to step up along the y-axis ; ; * We potentially swap coordinates to make sure Y1 >= Y2 ; ; ****************************************************************************** .STPY LDY Y1 ; Set A = Y = Y1 TYA LDX X1 ; Set X = X1 CPY Y2 ; If Y1 >= Y2, jump down to LI15, as the coordinates are BCS LI15 ; already in the order that we want DEC SWAP ; Otherwise decrement SWAP from 0 to $FF, to denote that ; we are swapping the coordinates around LDA X2 ; Swap the values of X1 and X2 STA X1 STX X2 TAX ; Set X = X1 LDA Y2 ; Swap the values of Y1 and Y2 STA Y1 STY Y2 TAY ; Set Y = A = Y1 .LI15 ; By this point we know the line is vertical-ish and ; Y1 >= Y2, so we're going from top to bottom as we go ; from Y1 to Y2 TXA ; Set A = bits 3-7 of X1 AND #%11111000 CLC ; The ylookup table lets us look up the 16-bit address ADC ylookupl,Y ; of the start of a character row containing a specific STA SC ; pixel, so this fetches the address for the start of LDA ylookuph,Y ; the character row containing the y-coordinate in Y, ADC #0 ; and adds it to the row offset we just calculated in A STA SC+1 TYA ; Set Y = Y mod 8 AND #7 ; TAY ; So Y is the pixel row within the character block where ; we want to start drawing TXA ; Set X = X1 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA TWOS,X ; Fetch a one-pixel byte from TWOS where pixel X is set, STA R2 ; and store it in R2 ; The following section calculates: ; ; P2 = P2 / Q2 ; = |delta_x| / |delta_y| ; ; using the log tables at logL and log to calculate: ; ; A = log(P2) - log(Q2) ; = log(|delta_x|) - log(|delta_y|) ; ; by first subtracting the low bytes of the logarithms ; from the table at LogL, and then subtracting the high ; bytes from the table at log, before applying the ; antilog to get the result of the division and putting ; it in P2 LDX P2 ; Set X = |delta_x| BEQ LIfudge ; If |delta_x| = 0, jump to LIfudge to return 0 as the ; result of the division LDA logL,X ; Set A = log(P2) - log(Q2) LDX Q2 ; = log(|delta_x|) - log(|delta_y|) SEC ; SBC logL,X ; by first subtracting the low bytes of ; log(P2) - log(Q2) BMI LIloG ; If A > 127, jump to LIloG LDX P2 ; And then subtracting the high bytes of LDA log,X ; log(P2) - log(Q2) so now A contains the high byte of LDX Q2 ; log(P2) - log(Q2) SBC log,X BCS LIlog3 ; If the subtraction fitted into one byte and didn't ; underflow, then log(P2) - log(Q2) < 256, so we jump to ; LIlog3 to return a result of 255 TAX ; Otherwise we set A to the A-th entry from the antilog LDA antilog,X ; table so the result of the division is now in A JMP LIlog2 ; Jump to LIlog2 to return the result .LIlog3 LDA #255 ; The division is very close to 1, so set A to the BNE LIlog2 ; closest possible answer to 256, i.e. 255, and jump to ; LIlog2 to return the result (this BNE is effectively a ; JMP as A is never zero) .LIloG LDX P2 ; Subtract the high bytes of log(P2) - log(Q2) so now A LDA log,X ; contains the high byte of log(P2) - log(Q2) LDX Q2 SBC log,X BCS LIlog3 ; If the subtraction fitted into one byte and didn't ; underflow, then log(P2) - log(Q2) < 256, so we jump to ; LIlog3 to return a result of 255 TAX ; Otherwise we set A to the A-th entry from the LDA antilogODD,X ; antilogODD so the result of the division is now in A .LIlog2 STA P2 ; Store the result of the division in P2, so we have: ; ; P2 = |delta_x| / |delta_y| .LIfudge SEC ; Set the C flag for the subtraction below LDX Q2 ; Set X = Q2 + 1 INX ; = |delta_y| + 1 ; ; We add 1 so we can skip the first pixel plot if the ; line is being drawn with swapped coordinates LDA X2 ; Set A = X2 - X1 SBC X1 BCC LFT ; If X2 < X1 then jump to LFT, as we need to draw the ; line to the left and down ; ****************************************************************************** ; ; Name: LOIN (Part 6 of 7) ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a steep line going up and left or down and right ; Deep dive: Elite's line-drawing algorithm ; ; ------------------------------------------------------------------------------ ; ; This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. ; If we get here, then: ; ; * The line is going up and left (no swap) or down and right (swap) ; ; * X1 < X2 and Y1 >= Y2 ; ; * Draw from (X1, Y1) at top left to (X2, Y2) at bottom right, omitting the ; first pixel ; ; ****************************************************************************** CLC ; Clear the C flag LDA SWAP ; If SWAP = 0 then we didn't swap the coordinates above, BEQ LI17 ; so jump down to LI17 to skip plotting the first pixel DEX ; Decrement the counter in X because we're about to plot ; the first pixel .LIL5 ; We now loop along the line from left to right, using X ; as a decreasing counter, and at each count we plot a ; single pixel using the pixel mask in R2 LDA R2 ; Fetch the pixel byte from R2 EOR (SC),Y ; Store R into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen .LI17 DEY ; Decrement Y to step up along the y-axis BPL LI16 ; If Y is positive we are still within the same ; character block, so skip to LI16 ; We now need to move up into the character block above, ; and each character row in screen memory takes up $140 ; bytes ($100 for the visible part and $20 for each of ; the blank borders on the side of the screen), so ; that's what we need to subtract from SC(1 0) ; ; We also know the C flag is clear, as we cleared it ; above, so we can subtract $13F in order to get the ; correct result LDA SC ; Set SC(1 0) = SC(1 0) - $140 SBC #$3F ; STA SC ; Starting with the low bytes LDA SCH ; And then subtracting the high bytes SBC #$01 STA SCH LDY #7 ; Set the pixel line to the last line in that character ; block .LI16 LDA S2 ; Set S2 = S2 + P2 to update the slope error ADC P2 STA S2 BCC LIC5 ; If the addition didn't overflow, jump to LIC5 LSR R2 ; Otherwise we just overflowed, so shift the single ; pixel in R2 to the right, so the next pixel we plot ; will be at the next x-coordinate along BCC LIC5 ; If the pixel didn't fall out of the right end of R ; into the C flag, then jump to LIC5 ROR R2 ; Otherwise we need to move over to the next character ; block, so first rotate R right so the set C flag goes ; back into the left end, giving %10000000 LDA SC ; Add 8 to SC, so SC(1 0) now points to the next ADC #8 ; character along to the right STA SC BCC LIC5 ; If the addition of the low bytes of SC overflowed, INC SC+1 ; increment the high byte CLC ; Clear the C flag .LIC5 DEX ; Decrement the counter in X BNE LIL5 ; If we haven't yet reached the right end of the line, ; loop back to LIL5 to plot the next pixel along LDY YSAV ; Restore Y from YSAV, so that it's preserved RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: LOIN (Part 7 of 7) ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a steep line going up and right or down and left ; Deep dive: Elite's line-drawing algorithm ; ; ------------------------------------------------------------------------------ ; ; This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. ; If we get here, then: ; ; * The line is going up and right (no swap) or down and left (swap) ; ; * X1 >= X2 and Y1 >= Y2 ; ; * Draw from (X1, Y1) at bottom left to (X2, Y2) at top right, omitting the ; first pixel ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; HL6 Contains an RTS ; ; ****************************************************************************** .LFT LDA SWAP ; If SWAP = 0 then we didn't swap the coordinates above, BEQ LI18 ; so jump down to LI18 to skip plotting the first pixel DEX ; Decrement the counter in X because we're about to plot ; the first pixel .LIL6 LDA R2 ; Fetch the pixel byte from R2 EOR (SC),Y ; Store R into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen .LI18 DEY ; Decrement Y to step up along the y-axis BPL LI19 ; If Y is positive we are still within the same ; character block, so skip to LI19 ; We now need to move up into the character block above, ; and each character row in screen memory takes up $140 ; bytes ($100 for the visible part and $20 for each of ; the blank borders on the side of the screen), so ; that's what we need to subtract from SC(1 0) ; ; We also know the C flag is clear, as we call LFT with ; a BCC, so we can subtract $13F in order to get the ; correct result LDA SC ; Set SC(1 0) = SC(1 0) - $140 SBC #$3F ; STA SC ; Starting with the low bytes LDA SCH ; And then subtracting the high bytes SBC #$01 STA SCH LDY #7 ; Set the pixel line to the last line in that character ; block .LI19 LDA S2 ; Set S2 = S2 + P2 to update the slope error ADC P2 STA S2 BCC LIC6 ; If the addition didn't overflow, jump to LIC6 ASL R2 ; Otherwise we just overflowed, so shift the single ; pixel in R2 to the left, so the next pixel we plot ; will be at the previous x-coordinate BCC LIC6 ; If the pixel didn't fall out of the left end of R2 ; into the C flag, then jump to LIC6 ROL R2 ; Otherwise we need to move over to the next character ; block, so first rotate R2 left so the set C flag goes ; back into the right end, giving %0000001 LDA SC ; Subtract 7 from SC, so SC(1 0) now points to the SBC #7 ; previous character along to the left STA SC BCS P%+4 ; If the subtraction of the low bytes of SC underflowed, DEC SCH ; decrement the high byte CLC ; Clear the C flag so it doesn't affect the additions ; if we loop back .LIC6 DEX ; Decrement the counter in X BNE LIL6 ; If we haven't yet reached the left end of the line, ; loop back to LIL6 to plot the next pixel along LDY YSAV ; Restore Y from YSAV, so that it's preserved .HL6 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: HLOIN ; Type: Subroutine ; Category: Drawing lines ; Summary: Draw a horizontal line from (X1, Y1) to (X2, Y1) ; Deep dive: Drawing pixels in the Commodore 64 version ; ; ****************************************************************************** .HLOIN STY YSAV ; Store Y into YSAV, so we can preserve it across the ; call to this subroutine LDX X1 ; Set X = X1 CPX X2 ; If X1 = X2 then the start and end points are the same, BEQ HL6 ; so return from the subroutine (as HL6 contains an RTS) BCC HL5 ; If X1 < X2, jump to HL5 to skip the following code, as ; (X1, Y1) is already the left point LDA X2 ; Swap the values of X1 and X2, so we know that (X1, Y1) STA X1 ; is on the left and (X2, Y1) is on the right STX X2 TAX ; Set X = X1 .HL5 DEC X2 ; Decrement X2 so we do not draw a pixel at the end ; point LDA Y1 ; Set the low byte of SC(1 0) to Y1 mod 8, which is the TAY ; pixel row within the character block at which we want AND #7 ; to draw our line (as each character block has 8 rows) STA SC LDA ylookuph,Y ; Set the top byte of SC(1 0) to the address of the STA SC+1 ; start of the character row to draw in, from the ; ylookup table TXA ; Set A = bits 3-7 of X1 AND #%11111000 CLC ; The ylookup table lets us look up the 16-bit address ADC ylookupl,Y ; of the start of a character row containing a specific TAY ; pixel, so this fetches the address for the start of ; the character row containing the y-coordinate in Y, ; and adds it to the row offset we just calculated in A, ; storing the result in Y BCC P%+4 ; If the addition overflowed, increment the high byte INC SC+1 ; of SC(1 0), so SC(1 0) + Y gives us the correct ; address of the start of the line .HL1 TXA ; Set T2 = bits 3-7 of X1, which will contain the AND #%11111000 ; character number of the start of the line * 8 STA T2 LDA X2 ; Set A = bits 3-7 of X2, which will contain the AND #%11111000 ; character number of the end of the line * 8 SEC ; Set A = A - T2, which will contain the number of SBC T2 ; character blocks we need to fill - 1 * 8 BEQ HL2 ; If A = 0 then the start and end character blocks are ; the same, so the whole line fits within one block, so ; jump down to HL2 to draw the line ; Otherwise the line spans multiple characters, so we ; start with the left character, then do any characters ; in the middle, and finish with the right character LSR A ; Set R2 = A / 8, so R2 now contains the number of LSR A ; character blocks we need to fill - 1 LSR A STA R2 LDA X1 ; Set X = X1 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA TWFR,X ; Fetch a ready-made byte with X pixels filled in at the ; right end of the byte (so the filled pixels start at ; point X and go all the way to the end of the byte), ; which is the shape we want for the left end of the ; line EOR (SC),Y ; Store this into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen, ; so we have now drawn the line's left cap TYA ; Set Y = Y + 8 so (SC),Y points to the next character ADC #8 ; block along, on the same pixel row as before TAY BCC P%+4 ; If the addition overflowed, increment the high byte INC SC+1 ; of SC(1 0), so SC(1 0) + Y gives us the correct ; address of the pixel LDX R2 ; Fetch the number of character blocks we need to fill ; from R2 DEX ; Decrement the number of character blocks in X BEQ HL3 ; If X = 0 then we only have the last block to do (i.e. ; the right cap), so jump down to HL3 to draw it CLC ; Otherwise clear the C flag so we can do some additions ; while we draw the character blocks with full-width ; lines in them .HLL1 LDA #%11111111 ; Store a full-width eight-pixel horizontal line in EOR (SC),Y ; SC(1 0) so that it draws the line on-screen, using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen TYA ; Set Y = Y + 8 so (SC),Y points to the next character ADC #8 ; block along, on the same pixel row as before TAY BCC P%+5 ; If the addition overflowed, increment the high byte INC SC+1 ; of SC(1 0), so SC(1 0) + Y gives us the correct CLC ; address of the start of the line ; ; We also clear the C flag so additions will work ; properly if we loop back for more DEX ; Decrement the number of character blocks in X BNE HLL1 ; Loop back to draw more full-width lines, if we have ; any more to draw .HL3 LDA X2 ; Now to draw the last character block at the right end AND #7 ; of the line, so set X = X2 mod 8, which is the TAX ; horizontal pixel number where the line ends LDA TWFL,X ; Fetch a ready-made byte with X pixels filled in at the ; left end of the byte (so the filled pixels start at ; the left edge and go up to point X), which is the ; shape we want for the right end of the line EOR (SC),Y ; Store this into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen, ; so we have now drawn the line's right cap LDY YSAV ; Restore Y from YSAV, so that it's preserved across the ; call to this subroutine RTS ; Return from the subroutine .HL2 ; If we get here then the entire horizontal line fits ; into one character block LDA X1 ; Set X = X1 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA TWFR,X ; Fetch a ready-made byte with X pixels filled in at the STA T2 ; right end of the byte (so the filled pixels start at ; point X and go all the way to the end of the byte) LDA X2 ; Set X = X2 mod 8, which is the horizontal pixel number AND #7 ; where the line ends TAX LDA TWFL,X ; Fetch a ready-made byte with X pixels filled in at the ; left end of the byte (so the filled pixels start at ; the left edge and go up to point X) AND T2 ; We now have two bytes, one (T2) containing pixels from ; the starting point X1 onwards, and the other (A) ; containing pixels up to the end point at X2, so we can ; get the actual line we want to draw by AND'ing them ; together. For example, if we want to draw a line from ; point 2 to point 5 (within the row of 8 pixels ; numbered from 0 to 7), we would have this: ; ; T2 = %00111111 ; A = %11111100 ; T2 AND A = %00111100 ; ; So we can stick T2 AND A in screen memory to get the ; line we want, which is what we do here by setting ; A = A AND T2 EOR (SC),Y ; Store our horizontal line byte into screen memory at STA (SC),Y ; SC(1 0), using EOR logic so it merges with whatever is ; already on-screen LDY YSAV ; Restore Y from YSAV, so that it's preserved RTS ; Return from the subroutine EQUD $F0E0C080 ; These bytes appear to be unused; they contain a copy EQUW $FCF8 ; of the TWFL variable, and the original source has a EQUB $FE ; commented out label .TWFL EQUD $1F3F7FFF ; These bytes appear to be unused; they contain a copy EQUD $0103070F ; of the TWFR variable, and the original source has a ; commented out label .TWFR ; ****************************************************************************** ; ; Name: DOT ; Type: Subroutine ; Category: Dashboard ; Summary: Draw a dash on the compass ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; COMX The screen pixel x-coordinate of the dash ; ; COMY The screen pixel y-coordinate of the dash ; ; COMC The colour and thickness of the dash: ; ; * #YELLOW = a double-height dash in yellow, for when ; the object in the compass is in front of us ; ; * #GREEN = a single-height dash in green, for when ; the object in the compass is behind us ; ; ****************************************************************************** .DOT LDA COMY ; Set Y1 = COMY, the y-coordinate of the dash STA Y1 LDA COMX ; Set X1 = COMX, the x-coordinate of the dash STA X1 LDA COMC ; Set COL = COMC, the colour byte for the dash STA COL CMP #YELLOW ; If the dash's colour is not yellow, jump to CPIX2 to BNE CPIX2 ; draw a single-height dash in the compass, as it is ; showing that the planet or station is behind us ; Otherwise the dash is yellow, which is in front of us, ; so fall through into CPIX4 to draw a double-height ; dash in the compass ; ****************************************************************************** ; ; Name: CPIX4 ; Type: Subroutine ; Category: Drawing pixels ; Summary: Draw a double-height dot on the dashboard ; ; ------------------------------------------------------------------------------ ; ; Draw a double-height dot (2 pixels high, 2 pixels wide). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X1 The screen pixel x-coordinate of the bottom-left corner ; of the dot ; ; Y1 The screen pixel y-coordinate of the bottom-left corner ; of the dot ; ; COL The colour of the dot as a character row byte ; ; ****************************************************************************** .CPIX4 JSR CPIX2 ; Call CPIX2 to draw a single-height dash at (X1, Y1) DEC Y1 ; Decrement Y1 ; Fall through into CPIX2 to draw a second single-height ; dash on the pixel row above the first one, to create a ; double-height dot ; ****************************************************************************** ; ; Name: CPIX2 ; Type: Subroutine ; Category: Drawing pixels ; Summary: Draw a single-height dash on the dashboard ; Deep dive: Drawing pixels in the Commodore 64 version ; ; ------------------------------------------------------------------------------ ; ; Draw a single-height multicolour bitmap mode dash (1 pixel high, 2 pixels ; wide). ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X1 The screen pixel x-coordinate of the dash ; ; Y1 The screen pixel y-coordinate of the dash ; ; COL The colour of the dash as a multicolour bitmap mode ; character row byte ; ; ****************************************************************************** .CPIX2 LDY Y1 ; Fetch the y-coordinate into Y LDA X1 ; Each character block contains 8 pixel rows, so to get AND #%11111000 ; the address of the first byte in the character block ; that we need to draw into, as an offset from the start ; of the row, we clear bits 0-2 CLC ; The ylookup table lets us look up the 16-bit address ADC ylookupl,Y ; of the start of a character row containing a specific STA SC ; pixel, so this fetches the address for the start of LDA ylookuph,Y ; the character row containing the y-coordinate in Y, ADC #0 ; and adds it to the row offset we just calculated in A STA SC+1 TYA ; Set Y to the y-coordinate mod 8, which will be the AND #7 ; number of the pixel row we need to draw within the TAY ; character block LDA X1 ; Set X = X1 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the pixel lies (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA CTWOS2,X ; Fetch a multicolour bitmap mode one-pixel byte with AND COL ; the pixel position at X, and AND with the colour byte ; so that pixel takes on the colour we want to draw ; (i.e. A is acting as a mask on the colour byte) ; ; Note that the CTWOS2 table contains two identical ; bitmap bytes for consecutive values of X, as each ; pixel is double-width and straddles two x-coordinates EOR (SC),Y ; Draw the pixel on-screen using EOR logic, so we can STA (SC),Y ; remove it later without ruining the background that's ; already on-screen ;JSR P%+3 ; These instructions are commented out in the original ;INX ; source LDA CTWOS2+2,X ; Fetch a multicolour bitmap mode one-pixel byte with ; the pixel position at X+1, so we can draw the right ; pixel of the dash (we add 2 to CTWOS2 as there are two ; repeated entries for X and X+1 in the table) BPL CP1 ; The CTWOS table has an extra two rows at the end of it ; that repeat the first two value, %11000000, so if we ; have not fetched that value, then the right pixel of ; the dash is in the same character block as the left ; pixel, so jump to CP1 to draw it LDA SC ; Otherwise the left pixel we drew was at the last CLC ; position of four in this character block, so we add ADC #8 ; 8 to the screen address to move onto the next block STA SC ; along (as there are 8 bytes in a character block) BCC P%+4 ; If the addition we just did overflowed, then increment INC SC+1 ; the high byte of SC(1 0), as this means we just moved ; into the right half of the screen row LDA CTWOS2+2,X ; Re-fetch the multicolour bitmap mode one-pixel byte, ; as we just overwrote A (the byte will still be the ; last byte from the table, which is correct as we want ; to draw the leftmost pixel in the next character along ; as the dash's right pixel) .CP1 AND COL ; Apply the colour mask to the pixel byte, as above EOR (SC),Y ; Draw the dash's right pixel according to the mask in STA (SC),Y ; A, with the colour in COL, using EOR logic, just as ; above RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: ECBLB2 ; Type: Subroutine ; Category: Dashboard ; Summary: Start up the E.C.M. (light up the indicator, start the countdown ; and make the E.C.M. sound) ; ; ****************************************************************************** .ECBLB2 LDA #32 ; Set the E.C.M. countdown timer in ECMA to 32 STA ECMA LDY #sfxecm ; Call the NOISE routine with Y = sfxecm to make the JSR NOISE ; sound of the E.C.M., returning from the subroutine ; using a tail call ; Fall through into ECBLB to light up the E.C.M. bulb ; ****************************************************************************** ; ; Name: ECBLB ; Type: Subroutine ; Category: Dashboard ; Summary: Light up the E.C.M. indicator bulb ("E") on the dashboard ; ; ****************************************************************************** .ECBLB LDA ECELL ; EOR the colour byte at ECELL with the colour of the EOR #BULBCOL ; E.C.M. indicator bulb, so this either zeroes the STA ECELL ; character block for the top part of the E.C.M. bulb, ; which will hide it, or it sets it to BULBCOL, which ; will show it (so this toggles the top part of the ; E.C.M. bulb) LDA ECELL+40 ; Do the same for the bottom part of the E.C.M. bulb EOR #BULBCOL STA ECELL+40 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: SPBLB ; Type: Subroutine ; Category: Dashboard ; Summary: Light up the space station indicator ("S") on the dashboard ; ; ****************************************************************************** .SPBLB LDA SCELL ; EOR the colour byte at SCELL with the colour of the EOR #BULBCOL ; space station indicator bulb, so this either zeroes STA SCELL ; the character block for the top part of the space ; station bulb, which will hide it, or it sets it to ; BULBCOL, which will show it (so this toggles the top ; part of the space station bulb) LDA SCELL+40 ; Do the same for the bottom part of the space station EOR #BULBCOL ; bulb STA SCELL+40 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: MSBAR ; Type: Subroutine ; Category: Dashboard ; Summary: Draw a specific indicator in the dashboard's missile bar ; ; ------------------------------------------------------------------------------ ; ; Each indicator is a character block, so we can change the colour by simply ; changing the relevant colour byte ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The number of the missile indicator to update (counting ; from right to left, so indicator NOMSL is the leftmost ; indicator) ; ; Y The new colour of the missile indicator: ; ; * #BLACK2 = black (no missile) ; ; * #RED2 = red (armed and locked) ; ; * #YELLOW2 = yellow/white (armed) ; ; * #GREEN2 = green (unarmed) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; X X is preserved ; ; Y Y is set to 0 ; ; ****************************************************************************** .MSBAR DEX ; Set A = X - 1 TXA ; INX ; So A is in the range 0 to 3, with 0 being the missile ; indicator on the right EOR #3 ; Flip A so it is in the range 0 to 3, but with 0 being ; the missile indicator on the left STY SC ; Swap A and Y around (using SC as temporary storage), TAY ; so we have: LDA SC ; ; * A = the colour of the missile indicator ; ; * Y = the indicator number (0 on left, 3 on right) STA MCELL,Y ; Set the Y-th colour byte at MCELL to the new colour of ; the indicator, which sets the colour of the character ; block for the Y-th indicator LDY #0 ; Set Y = 0 so we can return it from the subroutine RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: newosrdch ; Type: Subroutine ; Category: Tube ; Summary: The custom OSRDCH routine for reading characters ; Deep dive: 6502 Second Processor Tube communication ; ; ------------------------------------------------------------------------------ ; ; This routine is not used in this version of Elite. It is left over from the ; 650s Second Processor version. ; ; ****************************************************************************** .newosrdch JSR $FFFF ; This address is overwritten by the STARTUP routine to ; contain the original value of RDCHV, so this call acts ; just like a standard JSR OSRDCH call, and reads a ; character from the current input stream and stores it ; in A CMP #128 ; If A < 128 then skip the following three instructions, BCC P%+6 ; otherwise the character is invalid, so fall through ; into badkey to deal with it .badkey ; If we get here then the character we read is invalid, ; so we return a beep character LDA #7 ; Set A to the beep character CLC ; Clear the C flag RTS ; Return from the subroutine ; If we get here then A < 128 CMP #' ' ; If A >= ASCII " " then this is a valid alphanumerical BCS coolkey ; key press (as A is in the range 32 to 127), so jump ; down to coolkey to return this key press CMP #13 ; If A = 13 then this is the return character, so jump BEQ coolkey ; down to coolkey to return this key press CMP #21 ; If A <> 21 jump up to badkey BNE badkey .coolkey ; If we get here then the character we read is valid, so ; return it CLC ; Clear the C flag RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: WSCAN ; Type: Subroutine ; Category: Drawing the screen ; Summary: Wait for the vertical sync ; ; ------------------------------------------------------------------------------ ; ; Wait for vertical sync to occur on the video system - in other words, wait ; for the screen to start its refresh cycle, which it does 50 times a second ; (50Hz) on PAL systems, or 60 times a second (60Hz) on NTSC systems. ; ; We do this by monitoring the value of RASTCT, which is updated by the ; interrupt routine at COMIRQ1 as it draws the two different parts of the screen ; (the upper part containing the space view, and the lower part containing the ; dashboard). ; ; ****************************************************************************** .WSCAN PHA ; Store A on the stack so we can preserve it .WSC1 LDA RASTCT ; Wait until RASTCT is non-zero, which indicates that BEQ WSC1 ; the VIC-II is now drawing the dashboard .WSC2 LDA RASTCT ; Wait until RASTCT is zero, which indicates that the BNE WSC2 ; VIC-II is now drawing the top line of the visible ; screen PLA ; Restore A from the stack so it is unchanged RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: CHPR2 ; Type: Subroutine ; Category: Text ; Summary: Character print vector handler ; ; ------------------------------------------------------------------------------ ; ; This routine is set as the handler in CHRV, so it replaces the Kernal's ; character-printing routine. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The character to print ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; C flag The C flag is cleared ; ; ****************************************************************************** .CHPR2 CMP #123 ; If the character to print in A is outside of the range BCS whosentthisshit ; 13 to 122, jump to whosentthisshit to print nothing CMP #13 BCC whosentthisshit BNE CHPR ; If A is not 13, jump to CHPR to print the character, ; returning from the subroutine using a tail call LDA #12 ; If we get here then A is 13, so call CHPR with A = 12, JSR CHPR ; which will print a carriage return LDA #13 ; Set A = 13 so it is unchanged .whosentthisshit CLC ; Clear the C flag, as the CHPR routine does this and ; we need CHPR2 to act in the same way RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: R5 ; Type: Subroutine ; Category: Text ; Summary: Make a beep and jump back into the character-printing routine at ; CHPR ; ; ****************************************************************************** .R5 JSR BEEP ; Call the BEEP subroutine to make a short, high beep JMP RR4 ; Jump to RR4 to restore the registers and return from ; the subroutine using a tail call ; ****************************************************************************** ; ; Name: clss ; Type: Subroutine ; Category: Drawing the screen ; Summary: Clear the screen, move the text cursor to the top-left corner and ; jump back into the CHPR routine to print the next character ; ; ****************************************************************************** .clss JSR TT66simp ; Call TT66simp to clear the whole screen inside the box ; border, and move the text cursor to the top-left ; corner LDA K3 ; We called this routine from CHPR, which put the ; character we are printing into K3, so set A to the ; character number so we can jump back to CHPR to print ; it on the newly cleared screen JMP RRafter ; Jump back into the CHPR routine to print the character ; in A ; ****************************************************************************** ; ; Name: RR4S ; Type: Subroutine ; Category: Text ; Summary: A jump point that restores the registers and returns from the CHPR ; subroutine (so we can use a branch instruction to jump to RR4) ; ; ****************************************************************************** .RR4S JMP RR4 ; Jump to RR4 to restore the registers and return from ; the subroutine using a tail call (this JMP enables us ; to jump to RR4 using a branch to RR4S though this ; isn't actually done anywhere) ; ****************************************************************************** ; ; Name: TT67K ; Type: Subroutine ; Category: Text ; Summary: Print a newline ; ; ****************************************************************************** .TT67K ; This does the same as the existing TT67 routine, which ; is also present in this source, so it isn't clear why ; this duplicate exists ; ; In the original source this label is TT67, but ; because BeebAsm doesn't allow us to redefine labels, ; I have renamed it to TT67K LDA #12 ; Set A to a carriage return character ; Fall through into CHPR to print the newline ; ****************************************************************************** ; ; Name: CHPR ; Type: Subroutine ; Category: Text ; Summary: Print a character at the text cursor by poking into screen memory ; Deep dive: Drawing text ; ; ------------------------------------------------------------------------------ ; ; Print a character at the text cursor (XC, YC), do a beep, print a newline, ; or delete left (backspace). ; ; The CHPR2 sends characters here for printing if they are in the range 13-122. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The character to be printed. Can be one of the ; following: ; ; * 7 (beep) ; ; * 10 (line feed) ; ; * 11 (clear the top part of the screen and draw a ; border) ; ; * 12-13 (carriage return) ; ; * 32-95 (ASCII capital letters, numbers and ; punctuation) ; ; * 127 (delete the character to the left of the text ; cursor and move the cursor to the left) ; ; XC Contains the text column to print at (the x-coordinate) ; ; YC Contains the line number to print on (the y-coordinate) ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; A A is preserved ; ; X X is preserved ; ; Y Y is preserved ; ; C flag The C flag is cleared ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; RR4 Restore the registers and return from the subroutine ; ; RRafter A re-entry point from the clss routine to print the ; character in A ; ****************************************************************************** .CHPR STA K3 ; Store the A, X and Y registers, so we can restore STY YSAV2 ; them at the end (so they don't get changed by this STX XSAV2 ; routine) LDY QQ17 ; Load the QQ17 flag, which contains the text printing ; flags CPY #255 ; If QQ17 = 255 then printing is disabled, so jump to BEQ RR4S ; RR4 (via the JMP in RR4S) to restore the registers ; and return from the subroutine using a tail call .RRafter CMP #7 ; If this is a beep character (A = 7), jump to R5, BEQ R5 ; which will emit the beep, restore the registers and ; return from the subroutine CMP #32 ; If this is an ASCII character (A >= 32), jump to RR1 BCS RR1 ; below, which will print the character, restore the ; registers and return from the subroutine CMP #10 ; If this is control code 10 (line feed) then jump to BEQ RRX1 ; RRX1, which will move down a line, restore the ; registers and return from the subroutine .RRX2 LDX #1 ; If we get here, then this is control code 12 or 13, STX XC ; both of which are used. This code prints a newline, ; which we can achieve by moving the text cursor ; to the start of the line (carriage return) and down ; one line (line feed). These two lines do the first ; bit by setting XC = 1, and we then fall through into ; the line feed routine that's used by control code 10 .RRX1 CMP #13 ; If this is control code 13 (carriage return) then jump BEQ RR4S ; to RR4 (via the JMP in RR4S) to restore the registers ; and return from the subroutine using a tail call INC YC ; Increment the text cursor y-coordinate to move it ; down one row BNE RR4S ; Jump to RR4 via RR4S to restore the registers and ; return from the subroutine using a tail call .RR1 ; If we get here, then the character to print is an ; ASCII character in the range 32-95. The quickest way ; to display text on-screen is to poke the character ; pixel by pixel, directly into screen memory, so ; that's what the rest of this routine does ; ; The first step, then, is to get hold of the bitmap ; definition for the character we want to draw on the ; screen (i.e. we need the pixel shape of this ; character) ; ; The Commodore 64 version of Elite uses the same ; character bitmaps as the BBC Micro version of Elite, ; which in turn uses the characters from the BBC Micro's ; MOS operating system ; ; A copy of these bitmap definitions is embedded into ; this source code at page FONT, so page 0 of the font ; is at FONT, page 1 is at FONT+1, and page 2 at ; FONT+2 ; ; There are definitions for 32 characters in each of the ; three pages of MOS memory, as each definition takes up ; 8 bytes (8 rows of 8 pixels) and 32 * 8 = 256 bytes = ; 1 page. So: ; ; ASCII 32-63 are defined in $C000-$C0FF (page 0) ; ASCII 64-95 are defined in $C100-$C1FF (page 1) ; ASCII 96-126 are defined in $C200-$C2F0 (page 2) ; ; The following code reads the relevant character ; bitmap from the copied MOS bitmaps at FONT and pokes ; those values into the correct position in screen ; memory, thus printing the character on-screen ; ; It's a long way from 10 PRINT "Hello world!":GOTO 10 TAY ; Copy the character number from A to Y, as we are ; about to pull A apart to work out where this ; character definition lives in memory ; Now we want to set X to point to the relevant page ; number for this character - i.e. FONT to FONT+2 ; The following logic is easier to follow if we look ; at the three character number ranges in binary: ; ; Bit # 76543210 ; ; 32 = %00100000 Page 0 of bitmap definitions ; 63 = %00111111 ; ; 64 = %01000000 Page 1 of bitmap definitions ; 95 = %01011111 ; ; 96 = %01100000 Page 2 of bitmap definitions ; 125 = %01111101 ; ; We'll refer to this below LDX #HI(FONT)-1 ; Set X to point to the page before the first font page, ; which is HI(FONT) - 1 ASL A ; If bit 6 of the character is clear (A is 32-63) ASL A ; then skip the following instruction BCC P%+4 LDX #HI(FONT)+1 ; A is 64-126, so set X to point to the after the first ; font page, which is HI(FONT) + 1 ASL A ; If bit 5 of the character is clear (A is 64-95) BCC P%+3 ; then skip the following instruction INX ; Increment X ; ; By this point, we started with X = FONT-1, and then ; we did the following: ; ; If A = 32-63: skip then INX so X = FONT ; If A = 64-95: X = FONT+1 then skip so X = FONT+1 ; If A = 96-126: X = FONT+1 then INX so X = FONT+2 ; ; In other words, X points to the relevant page. But ; what about the value of A? That gets shifted to the ; left three times during the above code, which ; multiplies the number by 8 but also drops bits 7, 6 ; and 5 in the process. Look at the above binary ; figures and you can see that if we cleared bits 5-7, ; then that would change 32-53 to 0-31... but it would ; do exactly the same to 64-95 and 96-125. And because ; we also multiply this figure by 8, A now points to ; the start of the character's definition within its ; page (because there are 8 bytes per character ; definition) ; ; Or, to put it another way, X contains the high byte ; (the page) of the address of the definition that we ; want, while A contains the low byte (the offset into ; the page) of the address STA P+1 ; Store the address of this character's definition in STX P+2 ; P(2 1) LDA XC ; Fetch XC, the x-coordinate (column) of the text cursor ; into A CMP #31 ; If A >= 31, i.e. the text cursor past the right edge BCS RRX2 ; the screen, jump to RRX2 to move to column 1 LDA #$80 ; Set SC to $80 so we can use it in the calculation of STA SC ; the character's screen address below LDA YC ; Fetch YC, the y-coordinate (row) of the text cursor CMP #24 ; If the text cursor is on the screen (i.e. YC < 24, so BCC RR3 ; we are on rows 0-23), then jump to RR3 to print the ; character JMP clss ; Otherwise we are off the bottom of the screen, so call ; clss to clear the screen and draw a border box, ; before jumping back to RRafter with A set to the ; character to be printed at the top of the newly ; cleared screen .RR3 ; A contains the value of YC - the screen row where we ; want to print this character - so now we need to ; convert this into a screen address, so we can poke ; the character data to the right place in screen ; memory LSR A ; Set (A SC) = (A SC) >> 2 ROR SC ; = (YC $80) / 4 LSR A ; = (YC * 256 / 4) + ($80 / 4) ROR SC ; = YC * 64 + $20 ; ; This also clears the C flag, as the low bits of SC are ; all zeroes ADC YC ; Set A = A + YC ; ; So (A SC) = (A SC) + (YC 0) ; = YC * 64 + $20 + YC * 256 ; = YC * 320 + 32 ADC #HI(SCBASE) ; The low byte of the screen bitmap address in SCBASE is STA SC+1 ; always zero, so this does the following: ; ; SC(1 0) = SCBASE + (A SC) ; = SCBASE + YC * 320 + 32 ; ; So SC(1 0) contains the screen address we want to poke ; the character into, because: ; ; * The screen bitmap starts at SCBASE ; ; * Each character row of 40 character blocks takes up ; 40 * 8 = 320 bytes, and we want to print on row ; YC, so we add YC * 320 bytes to get to the correct ; character row ; ; * Because the game screen is 256 pixels wide and the ; Commodore 64 screen is 320 pixels wide, we have a ; 32-pixel margin on each side that we need to skip ; past, and 32 pixels is the width of four character ; blocks, each of which takes up eight bytes of ; bitmap memory, so we add another 4 * 8 = 32 bytes ; to cater for the indent LDA XC ; Set SC(1 0) = SC(1 0) + XC * 8 ASL A ; ASL A ; So SC(1 0) now points to the screen address of the ASL A ; character block in column XC, which is where we want ADC SC ; to draw our character STA SC BCC P%+4 INC SC+1 CPY #127 ; If the character number (which is in Y) <> 127, then BNE RR2 ; skip to RR2 to print that character, otherwise this is ; the delete character, so continue on DEC XC ; We want to delete the character to the left of the ; text cursor and move the cursor back one, so let's ; do that by decrementing YC. Note that this doesn't ; have anything to do with the actual deletion below, ; we're just updating the cursor so it's in the right ; position following the deletion DEC SC+1 ; Decrement the high byte of the screen address to point ; to the address of the current character, minus one ; page LDY #$F8 ; Set Y = $F8, so the following call to ZESNEW will ; count Y upwards from $F8 to $FF JSR ZESNEW ; Call ZESNEW, which zero-fills from address SC(1 0) + Y ; to SC(1 0) + $FF. SC(1 0) points to the character ; above the text cursor, and adding $FF to this would ; point to the cursor, so adding $F8 points to the ; character before the cursor, which is the one we want ; to delete. So this call zero-fills the character to ; the left of the cursor, which erases it from the ; screen BEQ RR4 ; We are done deleting, so restore the registers and ; return from the subroutine (this BNE is effectively ; a JMP as ZESNEW always returns with the Z flag set) .RR2 INC XC ; Once we print the character, we want to move the text ; cursor to the right, so we do this by incrementing ; XC. Note that this doesn't have anything to do ; with the actual printing below, we're just updating ; the cursor so it's in the right position following ; the print EQUB $2C ; Skip the next instruction by turning it into ; $2C $85 $08, or BIT $0885, which does nothing apart ; from affect the flags STA SC+1 ; This instruction has no effect, as it is always ; skipped, so perhaps this was accidentally left behind ; from development LDY #7 ; We want to print the 8 bytes of character data to the ; screen (one byte per row), so set up a counter in Y ; to count these bytes .RRL1 LDA (P+1),Y ; The character definition is at P(2 1) - we set this up ; above - so load the Y-th byte from P(2 1), which will ; contain the bitmap for the Y-th row of the character EOR (SC),Y ; If we EOR this value with the existing screen ; contents, then it's reversible (so reprinting the ; same character in the same place will revert the ; screen to what it looked like before we printed ; anything); this means that printing a white pixel ; onto a white background results in a black pixel, but ; that's a small price to pay for easily erasable text STA (SC),Y ; Store the Y-th byte at the screen address for this ; character location DEY ; Decrement the loop counter BPL RRL1 ; Loop back for the next byte to print to the screen LDY YC ; Set SC(1 0) to the address of the start of the current LDA celllookl,Y ; text row in screen RAM, by looking up the address from STA SC ; the celllookl and celllookh tables for the row given LDA celllookh,Y ; in YC STA SC+1 ; ; In the text view, screen RAM is used to determine the ; colour of each on-screen character, so SC(1 0) is now ; set to the address of the colour information for the ; start of the current text row LDY XC ; Set the contents of SC(1 0) + XC to COL2 LDA COL2 ; STA (SC),Y ; This sets the XC-th byte in SC(1 0) to COL2, which ; sets the colour information for the XC-th character in ; the current text row to COL2 - in other words, this ; sets the colour of the character we just drew to COL2 .RR4 LDY YSAV2 ; We're done printing, so restore the values of the LDX XSAV2 ; A, X and Y registers that we saved above and clear LDA K3 ; the C flag, so everything is back to how it was CLC RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TTX66K ; Type: Subroutine ; Category: Drawing the screen ; Summary: Clear the whole screen or just the space view (as appropriate), ; draw a border box, and if required, show the dashboard ; Deep dive: The split-screen mode in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; If this is the space view then clear the top part of the screen and draw a ; border box along the top and sides. If this is a text view, then clear the ; whole screen and draw a border box around all four sides of the screen. ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; BOX Just draw the border box along the top and sides ; ; ****************************************************************************** .TTX66K ; We start by resetting screen RAM for the text view, ; which lives at $6000 and has one byte that defines the ; palette for each character block (as we want to set ; the palette to white pixels on a black background) LDA #$04 ; Set SC(1 0) = $6004 STA SC ; LDA #$60 ; So this skips the four character border on the left STA SC+1 ; of the screen, so SC(1 0) points to the top-left ; character of the text view in screen RAM LDX #24 ; The text view has 24 character rows, so set a row ; counter in X .BOL3 LDA #$10 ; Set A to a colour data byte that sets colour 1 (white) ; for the foreground and colour 0 (black) for the ; background LDY #31 ; The game screen is 32 characters wide, so set a column ; counter in Y .BOL4 STA (SC),Y ; Set the Y-th colour data byte at SC(1 0) to A, to set ; white pixels on a black background for the text view DEY ; Decrement the column counter BPL BOL4 ; Loop back until we have set colour bytes for the whole ; row LDA SC ; Set SC(1 0) = SC(1 0) + 40 CLC ; ADC #40 ; So this moves SC(1 0) to the next row of colour data, STA SC ; as there are 40 characters on each row of the screen BCC P%+4 ; (with Elite only taking up the middle 32 characters) INC SC+1 DEX ; Decrement the row counter BNE BOL3 ; Loop back until we have set colour bytes for all 24 ; rows, by which time we will have reset the colour ; data for the whole text view ; Next, we zero the space view portion of the screen ; bitmap to clear the top part of the screen of any ; graphics or border boxes LDX #HI(SCBASE) ; Set X to the page number for the start of the screen ; bitmap in memory, which is at SCBASE, so we can use ; this as a page counter while resetting the screen ; bitmap .BOL1 JSR ZES1k ; Call ZES1k to zero-fill the page in X, which will ; clear one page of the screen bitmap by setting all ; pixels to the background colour INX ; Increment the page counter in X CPX #HI(DLOC%) ; Loop back to keep clearing pages until we reach DLOC%, BNE BOL1 ; which is the address of the start of the dashboard in ; the screen bitmap, so we loop back until we have ; cleared up to the start of the page that contains the ; start of the dashboard (which is most of the space ; view, but not the very last bit ; By this point X = HI(DLOC%), which we use in a couple ; of places below LDY #LO(DLOC%)-1 ; Set Y to the low byte of the address of the byte just ; before the first dashboard byte JSR ZES2k ; Call ZES2k to zero-fill from address (X SC) + Y to ; (X SC) + 1 ; ; X is HI(DLOC%), so this zero-fills the page at DLOC% ; from offset Y down to offset 1 ; ; So this resets the rest of the space view right up ; to the start of the dashboard, though it doesn't do ; the byte at offset 0 STA (SC),Y ; The call to ZES2k returns with both A = 0 and Y = 0, ; and SC(1 0) is set to the base address of the block we ; have just zeroed, so to zero the byte at offset 0, we ; just need to set SC(1 0) to 0 LDA #1 ; Move the text cursor to column 1 STA XC STA YC ; Move the text cursor to row 1 LDA QQ11 ; If QQ11 = 0 then this is the space view, so jump to BEQ wantSTEP ; wantdials via wantSTEP to display the dashboard in ; the lower portion of the screen CMP #13 ; If QQ11 = 13 then this is either the title screen or BNE P%+5 ; the rotating ship screen in the mission 1 briefing, so ; skip the following instruction as we do not want to ; display the dashboard .wantSTEP JMP wantdials ; If we get here then QQ11 = 0 or 13, which is the space ; view or title screen, so jump to wantdials to display ; the dashboard ; If we get here then this is not the space view or ; title screen, so we do not display the dashboard LDA #$81 ; Set abraxas = $81, so the colour of the lower part of STA abraxas ; the screen is determined by screen RAM at $6000 ; (i.e. for when the text view is being shown) LDA #%11000000 ; Clear bit 4 of caravanserai so that the lower part of STA caravanserai ; the screen (the dashboard) is shown in standard bitmap ; mode ; We set X = HI(DLOC%) above, so it points at the page ; that contains the start of the dashboard and we can ; use this to clear the rest of the screen bitmap, as ; we stopped when we reached the dashboard portion of ; the screen (and we aren't showing the dashboard in ; this view) .BOL2 JSR ZES1k ; Call ZES1k to zero-fill the page in X, which will ; clear part of a character row INX ; Increment the page in X CPX #HI(SCBASE)+$20 ; Loop back until we have cleared all $20 pages of the BNE BOL2 ; screen bitmap (i.e. from $4000 to $5FFF) LDX #0 ; Set the compass colour in COMC to the background STX COMC ; colour, so no compass dot gets drawn STX DFLAG ; Set DFLAG to 0 to indicate that there is no dashboard ; being shown on-screen INX ; Move the text cursor to column 1 (though we already STX XC ; did this, so this isn't strictly necessary) STX YC ; Move the text cursor to row 1 (though again, we ; already did this) JSR BLUEBAND ; Clear the borders along the edges of the space view, ; to hide any sprites that might be lurking there JSR zonkscanners ; Hide all ships on the scanner JSR NOSPRITES ; Call NOSPRITES to disable all sprites and remove them ; from the screen ; We now set the colour of the top row of the screen to ; yellow on black, for the border box LDY #31 ; The border box is 32 characters wide, so set a column ; counter in Y LDA #$70 ; Set A to a colour data byte that sets colour 7 ; (yellow) for the foreground and colour 0 (black) for ; the background, so the border box gets drawn in yellow .BOL5 STA $6004,Y ; Set the colour data for the Y-th character of the top ; character row to A, skipping the first four characters ; that form the left border DEY ; Decrement the column counter in Y BPL BOL5 ; Loop back until the whole top character row is set to ; a palette of yellow on black LDX QQ11 ; If QQ11 is one of the following: CPX #2 ; BEQ BOX ; * 2 (Buy Cargo screen) CPX #64 ; BEQ BOX ; * 64 (Long-range Chart) CPX #128 ; BEQ BOX ; * 128 (Short-range Chart) ; ; then jump to BOX to skip setting the third row to ; yellow on black (as otherwise this will affect the ; colours just below the line beneath the title text) ; We now set the colour of the third row of the screen ; to yellow on black, for the line beneath the title ; text LDY #31 ; The title box is 32 characters wide, so set a column ; counter in Y .BOL6 STA $6054,Y ; Set the colour data for the Y-th character of the ; third character row to A, skipping the first four ; characters that form the left border ; ; The address breaks down as follows: ; ; $6054 = $6000 + 2 * 40 + 4 ; ; as each row on-screen contains 40 characters, and we ; want to skip the first two rows, and indent to skip ; the left screen border DEY ; Decrement the column counter in Y BPL BOL6 ; Loop back until the whole of the third character row ; is set to a palette of yellow on black .BOX LDX #199 ; Draw a horizontal line across the screen at pixel JSR BOXS ; y-coordinate 199, to draw the bottom edge of the ; border box LDA #$FF ; This draws an eight-pixel line in character column 35 STA SCBASE+$1F1F ; on character row 24, which is within the ; four-character border to the right of the game screen ; and just within the lower portion of the screen (where ; the dashboard lives) ; ; The palette for this part of the screen is black on ; black, so the result isn't visible, and it's unclear ; what this is for; perhaps it was a visual check used ; during development to ensure that the border area was ; indeed not showing any pixels ; ; This write is manually reversed in the DEATH routine LDX #25 ; Set X = 25 so when we fall into BOX2, we draw the ; left and right edges of the border box at a height of ; 25 character rows rather than 18, so the box surrounds ; the entire screen, and not just the space view portion EQUB $2C ; Skip the first instruction of BOX2 by turning it into ; $2C $A2 $12, or BIT $12A2, which does nothing apart ; from affect the flags ; Fall into BOX2 to draw the left and right edges of the ; border box for the text view ; ****************************************************************************** ; ; Name: BOX2 ; Type: Subroutine ; Category: Drawing the screen ; Summary: Draw the left and right edges of the border box for the space view ; ; ****************************************************************************** .BOX2 LDX #18 ; The space view is 18 character rows tall, so set a ; row counter in X so we draw edges along the sides of ; the space view only STX T2 ; Store the value of X in T2 so we can retrieve it for ; the right edge below LDY #LO(SCBASE+3*8) ; Set (Y SC) to the address of the fourth byte in the STY SC ; screen bitmap, so we draw the border box in the LDY #HI(SCBASE+3*8) ; rightmost two pixels of the four-character screen ; border on the left LDA #%00000011 ; Set a pixel byte in A with the two rightmost pixels ; set, to use for drawing the left edge of the border ; box JSR BOXS2 ; Draw the left vertical edge of the border box LDY #LO(SCBASE+36*8) ; Set (Y SC) to the address of the first byte to the STY SC ; right of the game screen, skipping the four character LDY #HI(SCBASE+36*8) ; border on the left and the 32 characters of the game ; screen, so we draw the border box in the leftmost two ; pixels of the four-character screen border on the ; right LDA #%11000000 ; Set a pixel byte in A with the two leftmost pixels ; set, to use for drawing the right edge of the border ; box LDX T2 ; Set X to the height of the border box that we stored ; in T2 above JSR BOXS2 ; Draw the right vertical edge of the border box LDA #1 ; This draws a pixel in character column 35 on the first STA SCBASE+$118 ; character row, which is within the four-character ; border to the right of the game screen ; ; The palette for this part of the screen is black on ; black, so the result isn't visible, and it's unclear ; what this is for; perhaps it was a visual check used ; during development to ensure that the border area was ; indeed not showing any pixels ; ; This write is manually reversed in the DEATH routine LDX #0 ; Set X = 0 so we draw a horizontal line across the ; screen at pixel y-coordinate 0 ; Fall into BOXS to draw the top horizontal edge of the ; border box at y-coordinate 0 ; ****************************************************************************** ; ; Name: BOXS ; Type: Subroutine ; Category: Drawing the screen ; Summary: Draw a horizontal line across the screen at pixel y-coordinate X ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The pixel y-coordinate for the line ; ; ****************************************************************************** .BOXS STX Y1 ; Set Y1 = X LDX #0 ; Set X1 = 0 STX X1 DEX ; Set X2 = 255 STX X2 JMP HLOIN ; Call HLOIN to draw a horizontal line from (X1, Y1) to ; (X2, Y1), so that's from (0, X) to (255, X), and ; return from the subroutine using a tail call ; ****************************************************************************** ; ; Name: BOXS2 ; Type: Subroutine ; Category: Drawing the screen ; Summary: Draw a vertical line for the left or right border box edge ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A A screen bitmap pixel byte to use for the edge ; ; (Y SC) The address in screen bitmap memory of the character ; block at the top of the edge ; ; X The height of the line in character blocks (so the line ; will be 8 * X pixels tall) ; ; ****************************************************************************** .BOXS2 STA R2 ; Store the pixel byte for the edge in R2, so we can ; fetch it in the drawing loop STY SC+1 ; Set SC(1 0) = (Y SC), so SC(1 0) points to the address ; in the screen bitmap where we start drawing the line .BOXL2 LDY #7 ; We start by drawing the vertical line in all eight ; pixel rows in the character block, so set a pixel row ; counter in Y .BOXL3 LDA R2 ; Set A to the pixel byte containing set bits in the ; correct positions for the vertical line EOR (SC),Y ; Store A into screen memory at SC(1 0), using EOR STA (SC),Y ; logic so it merges with whatever is already on-screen DEY ; Decrement the pixel row counter BPL BOXL3 ; Loop back until we have drawn a vertical line across ; the whole character block LDA SC ; We have now drawn a whole character block, so we need CLC ; to move to the character row below, so add 320 ($140) ADC #$40 ; to SC(1 0) to move down one pixel line, as there are STA SC ; 320 bytes in each character row in the screen bitmap LDA SC+1 ADC #1 STA SC+1 DEX ; Decrement the character row counter BNE BOXL2 ; Loop back until we have drawn X character blocks RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: wantdials ; Type: Subroutine ; Category: Drawing the screen ; Summary: Show the dashboard on-screen ; Deep dive: The split-screen mode in Commodore 64 Elite ; Colouring the Commodore 64 bitmap screen ; ; ****************************************************************************** .wantdials JSR BOX2 ; Draw a border box around the space view LDA #$91 ; Set abraxas = $91, so the colour of the lower part of STA abraxas ; the screen is determined by screen RAM at $6400 ; (i.e. for when the dashboard is being shown) LDA #%11010000 ; Set bit 4 of caravanserai so that the lower part of STA caravanserai ; the screen (the dashboard) is shown in multicolour ; bitmap mode LDA DFLAG ; If DFLAG is non-zero then the dashboard is already BNE nearlyxmas ; being shown on-screen, so jump to nearlyxmas to skip ; displaying the dashboard on-screen ; We now copy the dashboard bitmap from the copy at ; DSTORE% into the screen bitmap, so the dashboard ; appears on-screen ; ; The bitmap is seven character rows in size, which is ; 7 * 40 * 7 = $8C0 bytes, so we need to copy this many ; bytes from DSTORE% to the screen bitmap address of the ; dashboard at DLOC% LDX #8 ; Set X = 8 so we copy the first eight pages of the ; dashboard bitmap from DSTORE% to screen memory LDA #LO(DSTORE%) ; Set V(1 0) = DSTORE% STA V ; LDA #HI(DSTORE%) ; So V(1 0) points to the copy of the dashboard image STA V+1 ; and colour data at DSTORE% LDA #LO(DLOC%) ; Set SC(1 0) = DLOC% STA SC ; LDA #HI(DLOC%) ; So SC(1 0) points to the address in the screen bitmap STA SC+1 ; of the start of the dashboard at DLOC% JSR mvblockK ; Copy X pages from V(1 0) to SC(1 0), which copies all ; eight pages of the dashboard bitmap from the copy at ; DSTORE% into the screen bitmap ; We have copied $800 bytes, so now for the other $C0 ; bytes LDY #$C0 ; Set Y = $C0 so we copy this many bytes LDX #1 ; Set X = 1 so we copy this many bytes within just one ; page JSR mvbllop ; Copy Y bytes from V(1 0) to SC(1 0), so this copies ; the rest of the dashboard bitmap to the screen JSR zonkscanners ; Hide all ships on the scanner JSR DIALS ; Call DIALS to update the dashboard .nearlyxmas JSR BLUEBAND ; Clear the borders along the edges of the space view, ; to hide any sprites that might be lurking there JSR NOSPRITES ; Call NOSPRITES to disable all sprites and remove them ; from the screen LDA #$FF ; Set DFLAG to $FF to indicate that the dashboard is now STA DFLAG ; being shown on-screen RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: zonkscanners ; Type: Subroutine ; Category: Drawing the screen ; Summary: Hide all ships on the scanner ; ; ****************************************************************************** .zonkscanners LDX #0 ; Set up a counter in X to work our way through all the ; ship slots in FRIN .zonkL LDA FRIN,X ; Fetch the ship type in slot X BEQ zonk1 ; If the slot contains 0 then it is empty and we have ; checked all the slots (as they are always shuffled ; down in the main loop to close up any gaps), so jump ; to zonk1 as we are done BMI zonk2 ; If the slot contains a ship type with bit 7 set, then ; it contains the planet or the sun, so jump down to ; zonk2 to skip this slot, as the planet and sun don't ; appear on the scanner JSR GINF ; Call GINF to get the address of the data block for ; ship slot X and store it in INF LDY #31 ; Clear bit 4 in the ship's byte #31, which hides it LDA (INF),Y ; from the scanner AND #%11101111 STA (INF),Y .zonk2 INX ; Increment X to point to the next ship slot BNE zonkL ; Loop back up to process the next slot (this BNE is ; effectively a JMP as X will never be zero) .zonk1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: BLUEBAND ; Type: Subroutine ; Category: Drawing the screen ; Summary: Clear two four-character borders along each side of the space view ; ; ------------------------------------------------------------------------------ ; ; The Elite game screen is 256 pixels wide but the Commodore 64 screen is 320 ; pixels wide, which leaves 64 pixels (eight character blocks). We therefore ; show the game in the middle of the screen, and clear a four-character border ; along the left and right edges of the space view. ; ; This prevents the explosion sprite from spilling out of the sides of the ; space view, as that sprite is configured to appear behind coloured pixels, and ; the borders are filled with pixels of colour 1 (which is mapped to black so ; the borders aren't visible). ; ; ****************************************************************************** .BLUEBAND LDX #LO(SCBASE) ; Set (Y X) = SCBASE so it contains the address of the LDY #HI(SCBASE) ; top-left corner of the four-character border along ; the left edge of the screen JSR BLUEBANDS ; Call BLUEBANDS to clear the left border LDX #LO(SCBASE+37*8) ; Set (Y X) = SCBASE so it contains the address of LDY #HI(SCBASE+37*8) ; character block 37 on the top row of the screen, which ; is the top-left corner of the border along the right ; edge of the screen (as there are four blocks for the ; left border and 32 blocks for the space view) ; Fall through into BLUEBANDS to clear the right border ; ****************************************************************************** ; ; Name: BLUEBANDS ; Type: Subroutine ; Category: Drawing the screen ; Summary: Clear a four-character border along one side of the space view ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; (Y X) The address of the top-left corner of the border strip ; to fill ; ; ****************************************************************************** .BLUEBANDS STX SC ; Set SC(1 0) = (Y X) STY SC+1 LDX #18 ; The space view is 144 pixels high, which is 18 ; character rows of eight pixels each, so set a row ; counter in X .BLUEL2 LDY #23 ; The border is 24 pixels wide (four characters of ; eight pixels each), so set a pixel byte counter in Y ; to cover a whole character row of 24 pixels .BLUEL1 LDA #%11111111 ; Set A to a pixel byte with every pixel in colour 1 ; ; Colour 1 is mapped to black, so this blanks the sides ; of the screen, and because it is a non-zero colour, it ; will cover over any sprites in the border that are set ; to appear behind the screen contents ; ; The explosion sprite is the only sprite to be ; configured this way ; ; As this process is only done when we change views, it ; means changing the screen won't leave any remnants of ; the explosion sprite behind in the screen border area STA (SC),Y ; Store the pixel byte in the Y-th byte of SC(1 0) DEY ; Decrement the pixel byte counter BPL BLUEL1 ; Loop back until we have done a whole character row ; We now need to move down into the character row below, ; and each 40-character row in screen memory takes up ; 40 * 8 = 320 bytes ($140), so that's what we need to ; add to SC(1 0) LDA SC ; Set SC(1 0) = SC(1 0) + $140 CLC ; ADC #$40 ; Starting with the low bytes STA SC LDA SC+1 ; And then adding the high bytes ADC #$01 STA SC+1 DEX ; Decrement the row counter in X BNE BLUEL2 ; Loop back until we have filled all 18 rows along the ; edges of the space view RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: TT66simp ; Type: Subroutine ; Category: Drawing the screen ; Summary: Clear the whole screen inside the border box, and move the text ; cursor to the top-left corner ; ; ****************************************************************************** .TT66simp LDX #8 ; We are going to clear character rows 1 through 23, ; so that's from pixel y-coordinate 8 onwards, so set a ; set a pixel y-coordinate counter in X LDY #0 ; Set Y = 0, so we can use it as a byte counter below CLC ; Clear the C flag so the addition below works .T6SL1 LDA ylookupl,X ; Set SC(1 0) to the address in screen memory of the STA SC ; start of the character row within the game screen that LDA ylookuph,X ; contains pixel y-coordinate Y STA SC+1 TYA ; Set A = 0, which we can use to zero screen memory ; We now zero a whole page of memory (256 bytes) at ; SC(1 0), using Y as a byte counter, starting from ; Y = 0 .T6SL2 STA (SC),Y ; Zero the Y-th byte of SC(1 0) DEY ; Decrement the byte counter BNE T6SL2 ; Loop back until we have zeroed a whole page of bytes ; (which corresponds to an entire character row of width ; 256 pixels, which is the width of the game screen) TXA ; Set X = X + 8 ADC #8 ; TAX ; So X now points to the pixel coordinate at the start ; of the next character row CMP #24*8 ; Loop back until we have cleared character rows 1 BCC T6SL1 ; through 23 (i.e. values of X from 8 to 23*8) INY ; Move the text cursor to column 1 STY XC STY YC ; Move the text cursor to row 1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: ZES1k ; Type: Subroutine ; Category: Utility routines ; Summary: Zero-fill the page whose number is in X ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; X The page we want to zero-fill ; ; ****************************************************************************** .ZES1k LDY #0 ; If we set Y = SC = 0 and fall through into ZESNEW STY SC ; below, then we will zero-fill 255 bytes starting from ; SC - in other words, we will zero-fill the whole of ; page X ; ****************************************************************************** ; ; Name: ZES2k ; Type: Subroutine ; Category: Utility routines ; Summary: Zero-fill a specific page ; ; ------------------------------------------------------------------------------ ; ; Zero-fill from address (X SC) + Y to (X SC) + 1. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The offset from (X SC) where we start zeroing, counting ; down to 1; if Y = 0, then the whole page is reset ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Z flag Z flag is set ; ; A A is 0 ; ; Y Y is 0 ; ; ****************************************************************************** .ZES2k LDA #0 ; Load A with the byte we want to fill the memory block ; with - i.e. zero STX SC+1 ; We want to zero-fill page X, so store this in the ; high byte of SC, so the 16-bit address in SC and ; SC+1 is now pointing to the SC-th byte of page X .ZEL1k STA (SC),Y ; Zero the Y-th byte of the block pointed to by SC, ; so that's effectively the Y-th byte before SC DEY ; Decrement the loop counter BNE ZEL1k ; Loop back to zero the next byte RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: ZESNEW ; Type: Subroutine ; Category: Utility routines ; Summary: Zero-fill memory from SC(1 0) to the end of the page ; ; ------------------------------------------------------------------------------ ; ; Zero-fill from address SC(1 0) + Y to SC(1 0) + $FF. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y The offset from SC(1 0) where we start zeroing, counting ; up to $FF ; ; SC(1 0) The starting address of the zero-fill ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; Z flag Z flag is set ; ; ****************************************************************************** .ZESNEW LDA #0 ; Load A with the byte we want to fill the memory block ; with - i.e. zero .ZESNEWL STA (SC),Y ; Zero the Y-th byte of the block pointed to by SC INY ; Increment the loop counter BNE ZESNEWL ; Loop back to zero the next byte RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: SETXC ; Type: Subroutine ; Category: Text ; Summary: An unused routine to move the text cursor to a specific column ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text column ; ; ****************************************************************************** .SETXC STA XC ; Store the new text column in XC ;JMP PUTBACK ; This instruction is commented out in the original ; source RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: SETYC ; Type: Subroutine ; Category: Text ; Summary: An unused routine to move the text cursor to a specific row ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; A The text row ; ; ****************************************************************************** .SETYC STA YC ; Store the new text row in YC ;JMP PUTBACK ; This instruction is commented out in the original ; source RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: mvblockK ; Type: Subroutine ; Category: Utility routines ; Summary: Copy a specific number of pages in memory ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; V(1 0) Source address ; ; SC(1 0) Destination address ; ; X Number of pages of memory to copy ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; mvbllop Only copy Y bytes, rather than a whole page ; ; ****************************************************************************** .mvblockK LDY #0 ; Set an index counter in Y .mvbllop LDA (V),Y ; Copy the Y-th byte from V(1 0) to SC(1 0) STA (SC),Y DEY ; Decrement the index counter BNE mvbllop ; Loop back until we have copied a whole page of bytes INC V+1 ; Increment the high bytes of V(1 0) and SC(1 0) to INC SC+1 ; point to the next page in memory DEX ; Decrement the page counter BNE mvbllop ; Loop back until we have copied X pages of memory RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: CLYNS ; Type: Subroutine ; Category: Drawing the screen ; Summary: Clear the bottom three text rows of the space view ; ; ------------------------------------------------------------------------------ ; ; This routine clears some space at the bottom of the screen and moves the text ; cursor to column 1, row 21. ; ; ****************************************************************************** .CLYNS LDA #0 ; Set the delay in DLY to 0, to indicate that we are STA DLY ; no longer showing an in-flight message, so any new ; in-flight messages will be shown instantly STA de ; Clear de, the flag that appends " DESTROYED" to the ; end of the next text token, so that it doesn't .CLYNS2 LDA #%11111111 ; Set DTW2 = %11111111 to denote that we are not STA DTW2 ; currently printing a word LDA #%10000000 ; Set bit 7 of QQ17 to switch standard tokens to STA QQ17 ; Sentence Case LDA #21 ; Move the text cursor to row 21, near the bottom of STA YC ; the screen LDA #1 ; Move the text cursor to column 1 STA XC LDA #HI(SCBASE)+$1A ; Set the high byte of SC(1 0) to SCBASE + $1A and the STA SC+1 ; low byte to $60 LDA #$60 ; STA SC ; We know that the low byte of SCBASE is zero, so this ; sets SC(1 0) as follows: ; ; SC(1 0) = SCBASE + $1A00 + $60 ; ; Each character row in the screen bitmap is 40 ; characters wide, and each character takes up 8 bytes, ; so 21 rows takes up 21 * 40 * 8 = 6720 = $1A40 bytes, ; and the first four characters of each character row ; are the blank screen margin either side of the game ; screen (and 4 * 8 = 32 = $20), so $1A60 is the screen ; bitmap address of the start of character row 22 within ; the game area, which is where we want our three blank ; rows to appear ; ; In other words, we need to blank screen memory from ; SC(1 0) onwards, for three character rows LDX #3 ; We want to clear three text rows, so set a counter in ; X for 3 rows .CLYLOOP2 LDA #0 ; Set A = 0, which we can use to zero screen memory TAY ; Set Y = 0, so we can use it as a byte counter .CLYLOOP STA (SC),Y ; Zero the Y-th byte of SC(1 0) DEY ; Decrement the byte counter BNE CLYLOOP ; Loop back until we have zeroed a whole page of bytes ; (which corresponds to an entire character row of width ; 256 pixels, which is the width of the game screen) CLC ; We have now blanked a whole text row, so we need to LDA SC ; move to the character row below, so add 320 ($140) ADC #$40 ; to SC(1 0) to move down one pixel line, as there are STA SC ; 320 bytes in each character row in the screen bitmap LDA SC+1 ADC #1 STA SC+1 DEX ; Decrement the row counter in X BNE CLYLOOP2 ; Loop back to blank another row, until we have done the ; number of rows in X ; Fall through into SCAN to return from the subroutine ; (as the first instruction of SCAN is an RTS) ; ****************************************************************************** ; ; Name: SCAN ; Type: Subroutine ; Category: Dashboard ; Summary: Display the current ship on the scanner ; Deep dive: The 3D scanner ; ; ------------------------------------------------------------------------------ ; ; This is used both to display a ship on the scanner, and to erase it again. ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; INWK The ship's data block ; ; ****************************************************************************** .SCR1 RTS ; Return from the subroutine .SCAN LDA QQ11 ; If QQ11 is non-zero then this is not the space view BNE SCR1 ; and there is no dashboard on-screen, so jump to SCR1 ; to return from the subroutine LDA INWK+31 ; Fetch the ship's scanner flag from byte #31 AND #%00010000 ; If bit 4 is clear then the ship should not be shown BEQ SCR1 ; on the scanner, so return from the subroutine (as SCR1 ; contains an RTS) LDX TYPE ; Fetch the ship's type from TYPE into X BMI SCR1 ; If this is the planet or the sun, then the type will ; have bit 7 set and we don't want to display it on the ; scanner, so return from the subroutine (as SCR1 ; contains an RTS) LDA scacol,X ; Set A to the scanner colour for this ship type from ; the X-th entry in the scacol table STA COL ; Store the scanner colour in COL so it can be used to ; draw this ship in the correct colour LDA INWK+1 ; If any of x_hi, y_hi and z_hi have a 1 in bit 6 or 7, ORA INWK+4 ; then the ship is too far away to be shown on the ORA INWK+7 ; scanner, so return from the subroutine (as SCR1 AND #%11000000 ; contains an RTS) BNE SCR1 ; If we get here, we know x_hi, y_hi and z_hi are all ; 63 (%00111111) or less ; Now, we convert the x_hi coordinate of the ship into ; the screen x-coordinate of the dot on the scanner, ; using the following: ; ; X1 = 123 + (x_sign x_hi) LDA INWK+1 ; Set A = x_hi CLC ; Clear the C flag so we can do addition below LDX INWK+2 ; Set X = x_sign BPL SC2 ; If x_sign is positive, skip the following EOR #%11111111 ; x_sign is negative, so flip the bits in A and add 1 ADC #1 ; to make it a negative number (bit 7 will now be set ; as we confirmed above that bits 6 and 7 are clear). So ; this gives A the sign of x_sign and gives it a value ; range of -63 (%11000001) to 0 .SC2 ADC #123 ; Set X1 = 123 + (x_sign x_hi) STA X1 ; Next, we convert the z_hi coordinate of the ship into ; the y-coordinate of the base of the ship's stick, ; like this: ; ; SC = 220 - (z_sign z_hi) / 4 ; ; though the following code actually does it like this: ; ; SC = 255 - (35 + z_hi / 4) LDA INWK+7 ; Set A = z_hi / 4 LSR A ; LSR A ; So A is in the range 0-15 CLC ; Clear the C flag for the addition below LDX INWK+8 ; Set X = z_sign BPL SC3 ; If z_sign is positive, skip the following EOR #%11111111 ; z_sign is negative, so flip the bits in A and set the SEC ; C flag. As above, this makes A negative, this time ; with a range of -16 (%11110000) to -1 (%11111111). And ; as we are about to do an ADC, the SEC effectively adds ; another 1 to that value, giving a range of -15 to 0 .SC3 ADC #83 ; Set A = 83 + A to give a number in the range 68 to 98 EOR #%11111111 ; Flip all the bits and store in Y2, so Y2 is in the STA SC ; range 157 to 187, with a higher z_hi giving a lower Y2 ; Now for the stick height, which we calculate using the ; following: ; ; A = - (y_sign y_hi) / 2 LDA INWK+4 ; Set A = y_hi / 2 LSR A CLC ; Clear the C flag LDX INWK+5 ; Set X = y_sign BMI SCD6 ; If y_sign is negative, skip the following, as we ; already have a positive value in A EOR #%11111111 ; y_sign is positive, so flip the bits in A and set the SEC ; C flag. This makes A negative, and as we are about to ; do an ADC below, the SEC effectively adds another 1 to ; that value to implement two's complement negation, so ; we don't need to add another 1 here .SCD6 ; We now have all the information we need to draw this ; ship on the scanner, namely: ; ; X1 = the screen x-coordinate of the ship's dot ; ; SC = the screen y-coordinate of the base of the ; stick ; ; A = the screen height of the ship's stick, with the ; correct sign for adding to the base of the stick ; to get the dot's y-coordinate ; ; First, though, we have to make sure the dot is inside ; the dashboard, by moving it if necessary ADC SC ; Set A = SC + A, so A now contains the y-coordinate of ; the end of the stick, plus the length of the stick, to ; give us the screen y-coordinate of the dot ;BPL ld246 ; This instruction is commented out in the original ; source CMP #146 ; If A >= 146, skip the following instruction, as 146 is BCS P%+4 ; the minimum allowed value of A LDA #146 ; A < 146, so set A to 146, the minimum allowed value ; for the y-coordinate of our ship's dot CMP #199 ; If A < 199, skip the following instruction, as 198 is BCC P%+4 ; the maximum allowed value of A .ld246 LDA #198 ; A >= 199, so set A to 198, the maximum allowed value ; for the y-coordinate of our ship's dot STA Y1 ; Store A in Y1, as it now contains the screen ; y-coordinate for the ship's dot, clipped so that it ; fits within the dashboard SEC ; Set A = A - SC to get the stick length, by reversing SBC SC ; the ADC SC we did above. This clears the C flag if the ; result is negative (i.e. the stick length is negative) ; and sets it if the result is positive (i.e. the stick ; length is negative) ; So now we have the following: ; ; X1 = the screen x-coordinate of the ship's dot, ; clipped to fit into the dashboard ; ; Y1 = the screen y-coordinate of the ship's dot, ; clipped to fit into the dashboard ; ; SC = the screen y-coordinate of the base of the ; stick ; ; A = the screen height of the ship's stick, with the ; correct sign for adding to the base of the stick ; to get the dot's y-coordinate ; ; C = 0 if A is negative, 1 if A is positive ; ; and we can get on with drawing the dot and stick PHP ; Store the flags (specifically the C flag) from the ; above subtraction ;BCS SC48 ; These instructions are commented out in the original ;EOR #$FF ; source. They would negate A if the C flag were set, ;ADC #1 ; which would reverse the direction of all the sticks, ; so you could turn your joystick around. Perhaps one of ; the authors' test sticks were easier to use upside ; down? Who knows... .SC48 PHA ; Store the stick height in A on the stack JSR CPIX4 ; Draw a double-height dot at (X1, Y1). This also leaves ; the following variables set up for the dot's top-right ; pixel, the last pixel to be drawn (as the dot gets ; drawn from the bottom up): ; ; SC(1 0) = screen address of the pixel's character ; block ; ; Y = number of the character row containing the pixel ; ; X = the pixel's number (0-3) in that row ; ; We can use there as the starting point for drawing the ; stick, if there is one LDA CTWOS2+2,X ; Load the same bitmap one-pixel byte that we just used AND COL ; for the top-right pixel, and mask it with the same STA X1 ; colour, storing the result in X1, so we can use it as ; the character row byte for the stick PLA ; Restore the stick height from the stack into A PLP ; Restore the flags from above, so the C flag once again ; reflects the sign of the stick height TAX ; Copy the stick height into X BEQ RTS ; If the stick height is zero, then there is no stick to ; draw, so return from the subroutine (as RTS contains ; an RTS) BCC VL3 ; If the C flag is clear then the stick height in A is ; negative, so jump down to VL3 .VLL1 ; If we get here then the stick length is positive (so ; the dot is below the ellipse and the stick is above ; the dot, and we need to draw the stick upwards from ; the dot) DEY ; We want to draw the stick upwards, so decrement the ; pixel row in Y BPL VL1 ; If Y is still positive then it correctly points at the ; line above, so jump to VL1 to skip the following LDY #7 ; We just decremented Y up through the top of the ; character block, so we need to move it to the last row ; in the character above, so set Y to 7, the number of ; the last row ; We now need to move up into the character block above, ; and each character row in screen memory takes up $140 ; bytes ($100 for the visible part and $20 for each of ; the blank borders on the side of the screen), so ; that's what we need to subtract from SC(1 0) LDA SC ; Set SC(1 0) = SC(1 0) - $140 SEC ; SBC #$40 ; Starting with the low bytes STA SC LDA SC+1 ; And then subtracting the high bytes SBC #$01 STA SC+1 .VL1 LDA X1 ; Set A to the character row byte for the stick, which ; we stored in X1 above, and which has the same pixel ; pattern as the bottom-right pixel of the dot (so the ; stick comes out of the right side of the dot) EOR (SC),Y ; Draw the stick on row Y of the character block using STA (SC),Y ; EOR logic DEX ; Decrement the (positive) stick height in X BNE VLL1 ; If we still have more stick to draw, jump up to VLL1 ; to draw the next pixel .RTS RTS ; Return from the subroutine ; If we get here then the stick length is negative (so ; the dot is above the ellipse and the stick is below ; the dot, and we need to draw the stick downwards from ; the dot) .VL3 INY ; We want to draw the stick downwards, so we first ; increment the row counter so that it's pointing to the ; bottom-right pixel in the dot (as opposed to the top- ; right pixel that the call to CPIX4 finished on) CPY #8 ; If the row number in Y is less than 8, then it BNE VLL2 ; correctly points at the next line down, so jump to ; VLL2 to skip the following LDY #0 ; We just incremented Y down through the bottom of the ; character block, so we need to move it to the first ; row in the character below, so set Y to 0, the number ; of the first row LDA SC ; Otherwise we need to move up into the character block ADC #$3F ; below, so add 320 ($140) to SC(1 0) to move down one STA SC ; pixel line, as there are 320 bytes in each character LDA SC+1 ; row in the screen bitmap ADC #1 ; STA SC+1 ; We know the C flag is set as we just passed through a ; BNE, so we only need to add $13F to get the result .VLL2 INY ; We want to draw the stick itself, heading downwards, ; so increment the pixel row in Y CPY #8 ; If the row number in Y is less than 8, then it BNE VL2 ; correctly points at the next line down, so jump to ; VL2 to skip the following LDY #0 ; We just incremented Y down through the bottom of the ; character block, so we need to move it to the first ; row in the character below, so set Y to 0, the number ; of the first row LDA SC ; Otherwise we need to move up into the character block ADC #$3F ; below, so add 320 ($140) to SC(1 0) to move down one STA SC ; pixel line, as there are 320 bytes in each character LDA SC+1 ; row in the screen bitmap ADC #1 ; STA SC+1 ; We know the C flag is set as we just passed through a ; BNE, so we only need to add $13F to get the result .VL2 LDA X1 ; Set A to the character row byte for the stick, which ; we stored in X1 above, and which has the same pixel ; pattern as the bottom-right pixel of the dot (so the ; stick comes out of the right side of the dot) EOR (SC),Y ; Draw the stick on row Y of the character block using STA (SC),Y ; EOR logic INX ; Increment the (negative) stick height in X BNE VLL2 ; If we still have more stick to draw, jump up to VLL2 ; to draw the next pixel RTS ; Return from the subroutine ; ****************************************************************************** ; ; Save ELTJ.bin ; ; ****************************************************************************** PRINT "ELITE J" PRINT "Assembled at ", ~CODE_J% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_J%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_J% PRINT "S.ELTJ ", ~CODE_J%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_J% SAVE "3-assembled-output/ELTJ.bin", CODE_J%, P%, LOAD% ; ****************************************************************************** ; ; ELITE K FILE ; ; Produces the binary file ELTK.bin that gets loaded by elite-checksum.py. ; ; ****************************************************************************** CODE_K% = P% LOAD_K% = LOAD% + P% - CODE% ; ****************************************************************************** ; ; Name: Music variables ; Type: Workspace ; Address: $B4CB to $B4D1 ; Category: Sound ; Summary: Variables that are used by the music player ; ; ****************************************************************************** .value0 EQUB 0 ; An unused counter that increments every time we ; process music command <#6> .value1 EQUB 0 ; Stores the voice control register for voice 1 .value2 EQUB 0 ; Stores the voice control register for voice 2 .value3 EQUB 0 ; Stores the voice control register for voice 3 .value4 EQUB 0 ; Stores the rest length for commands #8 and #15 IF _GMA_RELEASE .value5 EQUW $8888 ; The address before the start of the music data for the ; tune that is configured to play for docking, so this ; can be changed to alter the docking music ENDIF ; ****************************************************************************** ; ; Name: BDirqhere ; Type: Subroutine ; Category: Sound ; Summary: The interrupt routine for playing background music ; Deep dive: Music in Commodore 64 Elite ; ; ------------------------------------------------------------------------------ ; ; The label "BD" is used as a prefix throughout the music routines. This is a ; reference to the Blue Danube, which is the only bit of music that was included ; in the first release of Commodore 64 Elite (where it was used for the docking ; computer). The Elite Theme that plays on the title screen was added in a later ; release. ; ; The following comments appear in the original source: ; ; Music driver by Dave Dunn. ; ; BBC source code converted from Commodore disassembly extremely badly ; Jez. 13/4/85. ; ; Music system (c)1985 D.Dunn. ; Modified by IB,DB ; ; ------------------------------------------------------------------------------ ; ; Other entry points: ; ; BDskip1 Process the next nibble of music data in BDBUFF ; ; ****************************************************************************** .BDirqhere LDY #0 ; If the music counter is zero then we are not currently CPY counter ; processing a wait command, so jump to BDskip1 to BEQ BDskip1 ; process the current byte of music data in BDBUFF DEC counter ; Otherwise we are processing a rest command, so ; decrement the music counter while we continue to pause ; the music JMP BDlab1 ; And jump to BDlab1 to update the vibrato and control ; registers before returning from the subroutine .BDskip1 ; When we get here, Y is set to 0 ; ; This value is retained throughout the entire music ; interrupt routine, so whenever you see Y in these ; routines, it has a value of 0 LDA BDBUFF ; Set A to the current byte of music data in BDBUFF CMP #$10 ; If A >= $10 then the high nibble of A is non-zero, so BCS BDLABEL2 ; jump to BDLABEL2 to extract and process the command in ; the low nibble first (the command in the high nibble ; then gets moved into the low nibble to be processed ; next) TAX ; Set X to the low nibble of music data in A, so X is ; in the range 1 to 15 and contains the number of the ; music command we now need to process BNE BDLABEL ; If A > 0 then this is a valid command number in the ; range 1 to 15, so jump to BDLABEL to process the ; command ; If we get here then the nibble of music data in A is ; zero, which means "do nothing", so we just move on to ; the next data byte JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA BDBUFF ; Store the next music data byte in BDBUFF .BDLABEL2 AND #$0F ; Extract the low nibble of the music data into A TAX ; Set X to the low nibble of music data in A, so X is ; in the range 1 to 15 and contains the number of the ; music command we now need to process .BDLABEL LDA BDBUFF ; Shift the high nibble of BDBUFF into the low nibble, LSR A ; so it is available as the next nibble of music data LSR A ; to be processed, once we have finished processing the LSR A ; command in X LSR A STA BDBUFF ; We now process the current music command, which is in ; the low nibble of X, and in the range 1 to 15 LDA BDJMPTBL-1,X ; Modify the JMP command at BDJMP to jump to the X-th STA BDJMP+1 ; address in the BDJMPTBL table, to process the music LDA BDJMPTBH-1,X ; command in X STA BDJMP+2 ; ; This means that command <#1> jumps to BDRO1, command ; <#2> jumps to BDRO2, and so on up to command <#15>, ; which jumps to BDR15 .BDJMP JMP BDskip1 ; Jump to the correct routine for processing the music ; command in X (as this instruction gets modified) ; ****************************************************************************** ; ; Name: BDRO1 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#1 fh1 fl1> to set the frequency for voice ; 1 to (fh1 fl1) and the control register for voice 1 to value1 ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO1 JSR BDlab3 ; Fetch the next two music data bytes and set the ; frequency of voice 1 (high byte then low byte) JSR BDlab4 ; Set the voice control register for voice 1 to value1 JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDRO2 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#2 fh1 fl1> to set the frequency for voice ; 2 to (fh2 fl2) and the control register for voice 2 to value2 ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO2 JSR BDlab5 ; Fetch the next two music data bytes and set the ; frequency of voice 2 (high byte then low byte) and the ; vibrato variables for voice 2 JSR BDlab6 ; Set the voice control register for voice 2 to value2 JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDRO3 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#3 fh1 fl1> to set the frequency for voice ; 3 to (fh3 fl3) and the control register for voice 3 to value3 ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO3 JSR BDlab7 ; Fetch the next two music data bytes and set the ; frequency of voice 3 (high byte then low byte) and the ; vibrato variables for voice 3 JSR BDlab8 ; Set the voice control register for voice 3 to value3 JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDRO4 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#4 fh1 fl1 fh2 fl2> to set the frequencies ; and voice control registers for voices 1 and 2 ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO4 JSR BDlab3 ; Fetch the next two music data bytes and set the ; frequency of voice 1 (high byte then low byte) JSR BDlab5 ; Fetch the next two music data bytes and set the ; frequency of voice 2 (high byte then low byte) and the ; vibrato variables for voice 2 JSR BDlab4 ; Set the voice control register for voice 1 to value1 JSR BDlab6 ; Set the voice control register for voice 2 to value2 JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDRO5 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#5 fh1 fl1 fh2 fl2 fh3 fl3> to set the ; frequencies and voice control registers for voices 1, 2 and 3 ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO5 JSR BDlab3 ; Fetch the next two music data bytes and set the ; frequency of voice 1 (high byte then low byte) JSR BDlab5 ; Fetch the next two music data bytes and set the ; frequency of voice 2 (high byte then low byte) and the ; vibrato variables for voice 2 JSR BDlab7 ; Fetch the next two music data bytes and set the ; frequency of voice 3 (high byte then low byte) and the ; vibrato variables for voice 3 JSR BDlab4 ; Set the voice control register for voice 1 to value1 JSR BDlab6 ; Set the voice control register for voice 2 to value2 JSR BDlab8 ; Set the voice control register for voice 3 to value3 JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDRO6 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#6> to increment value0 and move on to the ; next nibble of music data ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO6 INC value0 ; Increment the counter in value0 ; ; This value is never read, so it could be a debugging ; command of some kind, or a counter that is not used ; by the music in Elite JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDRO15 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#15> to rest for 2 * value4 interrupts ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO15 LDA BDBUFF ; Slide the value %1000 (8) into the low nibble of SEC ; BDBUFF while shifting the original low nibble into ROL A ; the high nibble ASL A ; ASL A ; Because we process the low nibble first in each music ASL A ; data byte, this inserts command <#8> into the queue as STA BDBUFF ; the next command to be processed, after we fall ; through into BDRO8 to process another command <#8> ; first ; ; In other words, this processes two command <#8>s in a ; row, which implements a double-length rest ; Fall into BDRO8 to rest for value4 interrupts ; ****************************************************************************** ; ; Name: BDRO8 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#8> to rest for value4 interrupts ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO8 LDA value4 ; Set the music counter to value4, so we introduce a STA counter ; rest of value4 interrupts (i.e. a pause where we play ; no music) JMP BDirqhere ; Jump back to the start of the interrupt routine so the ; counter starts to count down ; ****************************************************************************** ; ; Name: BDRO7 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#7 ad1 ad2 ad3 sr1 sr2 sr3> to set three ; voices' attack and decay length, sustain volume and release length ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO7 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$5 ; Set SID register $5 to the music data byte we just ; fetched, which sets the attack and decay length for ; voice 1 as follows: ; ; * Bits 0-3 = decay length ; ; * Bits 4-7 = attack length JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$C ; Set SID register $C to the music data byte we just ; fetched, which sets the attack and decay length for ; voice 2 as follows: ; ; * Bits 0-3 = decay length ; ; * Bits 4-7 = attack length JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$13 ; Set SID register $12 to the music data byte we just ; fetched, which sets the attack and decay length for ; voice 3 as follows: ; ; * Bits 0-3 = decay length ; ; * Bits 4-7 = attack length JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$6 ; Set SID register $6 to the music data byte we just ; fetched, which sets the release length and sustain ; volume for voice 1 as follows: ; ; * Bits 0-3 = release length ; ; * Bits 4-7 = sustain volume JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$D ; Set SID register $D to the music data byte we just ; fetched, which sets the release length and sustain ; volume for voice 2 as follows: ; ; * Bits 0-3 = release length ; ; * Bits 4-7 = sustain volume JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$14 ; Set SID register $14 to the music data byte we just ; fetched, which sets the release length and sustain ; volume for voice 4 as follows: ; ; * Bits 0-3 = release length ; ; * Bits 4-7 = sustain volume JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDRO9 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#9> to restart the current tune ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO9 LDA #0 ; Clear the current music data byte, which discards the STA BDBUFF ; next nibble if there is one (so this flushes any data ; from the current pipeline) LDA BDdataptr3 ; Set BDdataptr1(1 0) = BDdataptr3(1 0) STA BDdataptr1 ; LDA BDdataptr4 ; So this sets the data pointer in BDdataptr1(1 0) back STA BDdataptr2 ; to the original value that we gave it at the start of ; the BDENTRY routine when we started playing this tune, ; which we stored in BDdataptr3(1 0) ; ; In other words, this restarts the current tune JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDRO10 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#10 h1 l1 h2 l2 h3 l3> to set the pulse ; width to all three voices ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO10 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$2 ; Set SID register $2 to the music data byte we just ; fetched, which sets the high byte of the pulse width ; for voice 1 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$3 ; Set SID register $3 to the music data byte we just ; fetched, which sets the low byte of the pulse width ; for voice 1 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$9 ; Set SID register $9 to the music data byte we just ; fetched, which sets the high byte of the pulse width ; for voice 2 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$A ; Set SID register $A to the music data byte we just ; fetched, which sets the low byte of the pulse width ; for voice 2 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$10 ; Set SID register $10 to the music data byte we just ; fetched, which sets the high byte of the pulse width ; for voice 3 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$11 ; Set SID register $11 to the music data byte we just ; fetched, which sets the low byte of the pulse width ; for voice 3 JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDRO11 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#11>, which does the same as command <#9> ; and restarts the current tune ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO11 JMP BDRO9 ; Jump to BDRO9 to process command <#9> (so command ; <#11> is the same as command <#9> and restarts the ; current tune) ; ****************************************************************************** ; ; Name: BDRO12 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#12 n> to set value4 = n, which sets the ; rest length for commands #8 and #15 ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO12 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA value4 ; Set value4 to the value of the byte we just fetched, ; which sets the rest length used in commands #8 and #15 JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDRO13 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#13 v1 v2 v3> to set value1, value2, value3 ; to the voice control register values for commands <#1> to <#3> ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO13 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA value1 ; Set value1 to the value of the byte we just fetched, ; which is used to set the voice control register for ; voice 1 in command <#1> JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA value2 ; Set value2 to the value of the byte we just fetched, ; which is used to set the voice control register for ; voice 2 in command <#2> JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA value3 ; Set value3 to the value of the byte we just fetched, ; which is used to set the voice control register for ; voice 3 in command <#3> JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDRO14 ; Type: Subroutine ; Category: Sound ; Summary: Process music command <#14 vf fc cf> to set the volume and filter ; modes, filter control and filter cut-off frequency ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDRO14 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$18 ; Set SID register $18 to the music data byte we just ; fetched, which sets the volume and filter modes as ; follows: ; ; * Bits 0-3: volume (0 to 15) ; ; * Bit 4 set: enable the low-pass filter ; ; * Bit 5 set: enable the bandpass filter ; ; * Bit 6 set: enable the high-pass filter ; ; * Bit 7 set: disable voice 1 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$17 ; Set SID register $17 to the music data byte we just ; fetched, which sets the filter control as follows: ; ; * Bit 0 set: voice 1 filtered ; ; * Bit 1 set: voice 2 filtered ; ; * Bit 2 set: voice 3 filtered ; ; * Bit 3 set: external voice filtered ; ; * Bits 4-7: filter resonance JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$16 ; Set SID register $16 to the music data byte we just ; fetched, which sets bits 3 to 10 of the filter cut-off ; frequency JMP BDskip1 ; Jump to BDskip1 to process the next nibble of music ; data ; ****************************************************************************** ; ; Name: BDlab4 ; Type: Subroutine ; Category: Sound ; Summary: Set the voice control register for voice 1 to value1 ; ; ****************************************************************************** .BDlab4 LDA value1 ; Set A to value1, to use as the new value of the voice ; control register for voice 1 STY SID+$4 ; Zero SID register $4 (the voice control register for ; voice 1), to reset the voice control STA SID+$4 ; Set SID register $4 (the voice control register for ; voice 1) to the music data byte that we just fetched ; in A, so control the voice as follows: ; ; * Bit 0: 0 = voice off, release cycle ; 1 = voice on, attack-decay-sustain cycle ; ; * Bit 1 set = synchronization enabled ; ; * Bit 2 set = ring modulation enabled ; ; * Bit 3 set = disable voice, reset noise generator ; ; * Bit 4 set = triangle waveform enabled ; ; * Bit 5 set = saw waveform enabled ; ; * Bit 6 set = square waveform enabled ; ; * Bit 7 set = noise waveform enabled RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: BDlab6 ; Type: Subroutine ; Category: Sound ; Summary: Set the voice control register for voice 2 to value2 ; ; ****************************************************************************** .BDlab6 LDA value2 ; Set A to value2, to use as the new value of the voice ; control register for voice 2 STY SID+$B ; Zero SID register $B (the voice control register for ; voice 2), to reset the voice control STA SID+$B ; Set SID register $B (the voice control register for ; voice 2) to the music data byte that we just fetched ; in A, so control the voice as follows: ; ; * Bit 0: 0 = voice off, release cycle ; 1 = voice on, attack-decay-sustain cycle ; ; * Bit 1 set = synchronization enabled ; ; * Bit 2 set = ring modulation enabled ; ; * Bit 3 set = disable voice, reset noise generator ; ; * Bit 4 set = triangle waveform enabled ; ; * Bit 5 set = saw waveform enabled ; ; * Bit 6 set = square waveform enabled ; ; * Bit 7 set = noise waveform enabled RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: BDlab8 ; Type: Subroutine ; Category: Sound ; Summary: Set the voice control register for voice 3 to value3 ; ; ****************************************************************************** .BDlab8 LDA value3 ; Set A to value3, to use as the new value of the voice ; control register for voice 3 STY SID+$12 ; Zero SID register $12 (the voice control register for ; voice 3), to reset the voice control STA SID+$12 ; Set SID register $12 (the voice control register for ; voice 3) to the music data byte that we just fetched ; in A, so control the voice as follows: ; ; * Bit 0: 0 = voice off, release cycle ; 1 = voice on, attack-decay-sustain cycle ; ; * Bit 1 set = synchronization enabled ; ; * Bit 2 set = ring modulation enabled ; ; * Bit 3 set = disable voice, reset noise generator ; ; * Bit 4 set = triangle waveform enabled ; ; * Bit 5 set = saw waveform enabled ; ; * Bit 6 set = square waveform enabled ; ; * Bit 7 set = noise waveform enabled RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: BDlab19 ; Type: Subroutine ; Category: Sound ; Summary: Increment the music data pointer in BDdataptr1(1 0) and fetch the ; next data byte into A ; ; ------------------------------------------------------------------------------ ; ; Arguments: ; ; Y Y is always 0 ; ; ------------------------------------------------------------------------------ ; ; Returns: ; ; BDdataptr1(1 0) Incremented to point to the next music data byte ; ; A The next music data byte ; ; ****************************************************************************** .BDlab19 INC BDdataptr1 ; Increment the data pointer in BDdataptr1(1 0) BNE BDskipme1 INC BDdataptr1+1 .BDskipme1 LDA (BDdataptr1),Y ; Y is zero, so this sets A to the next byte of music ; data from BDdataptr1(1 0) ; ; We have to include an index of Y as the 6502 doesn't ; have an instruction of the form LDA (BDdataptr1) RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: BDlab3 ; Type: Subroutine ; Category: Sound ; Summary: Fetch the next two music data bytes and set the frequency of ; voice 1 (high byte then low byte) ; ; ****************************************************************************** .BDlab3 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$1 ; Set SID register $1 to the music data byte we just ; fetched, which sets the high byte of the frequency ; for voice 1 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$0 ; Set SID register $0 to the music data byte we just ; fetched, which sets the low byte of the frequency ; for voice 1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: BDlab5 ; Type: Subroutine ; Category: Sound ; Summary: Fetch the next two music data bytes and set the frequency of ; voice 2 (high byte then low byte) and the vibrato variables ; ; ****************************************************************************** .BDlab5 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$8 ; Set SID register $8 to the music data byte we just ; fetched, which sets the high byte of the frequency ; for voice 2 STA voice2lo1 ; Store the high byte in the voice2lo variables STA voice2lo2 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$7 ; Set SID register $7 to the music data byte we just ; fetched, which sets the low byte of the frequency ; for voice 2 STA voice2hi1 ; Store the low byte in the voice2hi variables STA voice2hi2 ; So by this point we have the following: ; ; (voice2lo1 voice2hi1) = frequency ; ; (voice2lo2 voice2hi2) = frequency ; ; Note that the variable naming here is a bit odd, as ; the high bytes are in the voice2lo variables, and the ; low bytes are in the voice2hi variables ; ; These are the names from the original source code, so ; let's roll with it CLC ; Clear the C flag for the addition below CLD ; Clear the D flag to make sure we are in binary mode, ; as we are in an interrupt handler and can't be sure of ; the flag state on entry LDA #32 ; Set (voice2lo2 voice2hi2) = (voice2lo2 voice2hi2) + 32 ADC voice2hi2 ; STA voice2hi2 ; So the second frequency in (voice2lo2 voice2hi2) is BCC BDruts1 ; now a bit higher than the first frequency, which we INC voice2lo2 ; can use to add vibrato to voice 2 .BDruts1 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: BDlab7 ; Type: Subroutine ; Category: Sound ; Summary: Fetch the next two music data bytes and set the frequency of ; voice 3 (high byte then low byte) and the vibrato variables ; ; ****************************************************************************** .BDlab7 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$F ; Set SID register $F to the music data byte we just ; fetched, which sets the high byte of the frequency ; for voice 3 STA voice3lo1 ; Store the high byte in the voice3lo variables STA voice3lo2 JSR BDlab19 ; Increment the music data pointer in BDdataptr1(1 0) ; and fetch the next music data byte into A STA SID+$E ; Set SID register $E to the music data byte we just ; fetched, which sets the low byte of the frequency ; for voice 3 STA voice3hi1 ; Store the low byte in the voice3hi variables STA voice3hi2 ; So by this point we have the following: ; ; (voice3lo1 voice3hi1) = frequency ; ; (voice3lo2 voice3hi2) = frequency ; ; Note that the variable naming here is a bit odd, as ; the high bytes are in the voice3lo variables, and the ; low bytes are in the voice3hi variables ; ; These are the names from the original source code, so ; let's roll with it CLC ; Clear the C flag for the addition below CLD ; Clear the D flag to make sure we are in binary mode, ; as we are in an interrupt handler and can't be sure of ; the flag state on entry IF _GMA_RELEASE LDA #37 ; Set A to the frequency change used when applying ; vibrato to voice 3 (voice 2 applies a vibrato of 32, ; so this sets a bigger vibrato frequency change for ; voice 3) ELIF _SOURCE_DISK LDA #32 ; Set A to the frequency change used when applying ; vibrato to voice 3 (this is the same value as for ; voice 2) ENDIF ADC voice3hi2 ; Set (voice3lo2 voice3hi2) = (voice3lo2 voice3hi2) + A STA voice3hi2 ; BCC BDruts2 ; So the second frequency in (voice3lo2 voice3hi2) is INC voice3lo2 ; now a bit higher than the first frequency, which we ; can use to add vibrato to voice 3 .BDruts2 RTS ; Return from the subroutine ; ****************************************************************************** ; ; Name: BDENTRY ; Type: Subroutine ; Category: Sound ; Summary: Start playing a new tune as background music ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDENTRY LDA #0 ; Set BDBUFF = 0 to reset the current music data byte in STA BDBUFF ; BDBUFF STA counter ; Set counter = 0 to reset the rest counter STA vibrato2 ; Set vibrato2 = 0 to reset the vibrato counter for ; voice 2 STA vibrato3 ; Set vibrato3 = 0 to reset the vibrato counter for ; voice 3 ; We now zero all the SID registers from $01 to $18 to ; reset the sound chip (though it doesn't zero the first ; byte at $00, for some reason) LDX #$18 ; Set a byte counter in X .BDloop2 STA SID,X ; Zero the X-th byte of the SID registers DEX ; Decrement the counter in X BNE BDloop2 ; Loop back until we have zeroed all the registers from ; $01 to $18 IF _GMA_RELEASE LDA value5 ; Set A to the low byte of value5, which is set to the ; address before the start of the tune that is ; configured to play for docking ELIF _SOURCE_DISK LDA #LO(musicstart) ; Set A to the low byte of musicstart, which is the ; address before the start of the docking music ENDIF STA BDdataptr1 ; Set BDdataptr1 to the low byte of the music to play STA BDdataptr3 ; Set BDdataptr3 to the low byte of the music to play IF _GMA_RELEASE LDA value5+1 ; Set A to the high byte of value5, which is set to the ; address before the start of the tune that is ; configured to play for docking ELIF _SOURCE_DISK LDA #HI(musicstart) ; Set A to the high byte of musicstart, which is the ; address before the start of the docking music ENDIF STA BDdataptr2 ; Set BDdataptr2 to the high byte of the music to play, ; so BDdataptr1(1 0) is the address of the music to play ; (as BDdataptr2 = BDdataptr1 + 1) STA BDdataptr4 ; Set BDdataptr4 to the high byte of the music to play, ; so BDdataptr3(1 0) is the address of the music to play ; (as BDdataptr4 = BDdataptr3 + 1) LDA #%00001111 ; Set SID register $18 to control the sound as follows: STA SID+$18 ; ; * Bits 0-3: set the volume to 15 (maximum) ; ; * Bit 4 clear: disable the low-pass filter ; ; * Bit 5 clear: disable the bandpass filter ; ; * Bit 6 clear: disable the high-pass filter ; ; * Bit 7 clear: enable voice 3 ;SEI ; This instruction is commented out in the original ; source RTS ; Return from the subroutine ;point IRQ to start ; These instructions are commented out in the original ; ; source ;LDA #LO(BDirqhere) ;STA $0314 ;LDA #HI(BDirqhere) ;STA $0315 ; ;CLI ; ;BRK ; ;re enter monitor! ; ****************************************************************************** ; ; Name: BDlab24 ; Type: Subroutine ; Category: Sound ; Summary: Apply a vibrato frequency change to voice 2 ; ; ****************************************************************************** LDA #0 ; Reset the vibrato counter for voice 2 so it starts to STA vibrato2 ; count up towards the next vibrato change once again LDA #$AE ; Modify the BEQ instruction at BDbeqmod1 so next time STA BDbeqmod1+1 ; it jumps to the second half of this routine LDA voice2lo2 ; Set SID registers $7 and $8 to the vibrato frequency STA SID+$8 ; in (voice2lo2 voice2hi2), which sets the frequency LDA voice2hi2 ; for voice 2 to the second (i.e. the higher) frequency STA SID+$7 ; that we set up for vibrato in BDlab5 JMP BDlab21 ; Jump to BDlab21 to clean up and return from the ; interrupt routine .BDlab24 LDA #0 ; Reset the vibrato counter for voice 2 so it starts to STA vibrato2 ; count up towards the next vibrato change once again LDA #$98 ; Modify the BEQ instruction at BDbeqmod1 so next time STA BDbeqmod1+1 ; it jumps to the first half of this routine LDA voice2lo1 ; Set SID registers $7 and $8 to the vibrato frequency STA SID+$8 ; in (voice2lo1 voice2hi1), which sets the frequency LDA voice2hi1 ; for voice 2 to the first (i.e. the lower) frequency STA SID+$7 ; that we set up for vibrato in BDlab5 JMP BDlab21 ; Jump to BDlab21 to clean up and return from the ; interrupt routine ; ****************************************************************************** ; ; Name: BDlab23 ; Type: Subroutine ; Category: Sound ; Summary: Apply a vibrato frequency change to voice 3 ; ; ****************************************************************************** LDA #0 ; Reset the vibrato counter for voice 3 so it starts to STA vibrato3 ; count up towards the next vibrato change once again LDA #$E2 ; Modify the BEQ instruction at BDbeqmod2 so next time STA BDbeqmod2+1 ; it jumps to the second half of this routine LDA voice3lo2 ; Set SID registers $E and $F to the vibrato frequency STA SID+$F ; in (voice3lo2 voice3hi2), which sets the frequency LDA voice3hi2 ; for voice 3 to the second (i.e. the higher) frequency STA SID+$E ; that we set up for vibrato in BDlab7 JMP BDlab21 ; Jump to BDlab21 to clean up and return from the ; interrupt routine .BDlab23 LDA #0 ; Reset the vibrato counter for voice 3 so it starts to STA vibrato3 ; count up towards the next vibrato change once again LDA #$CC ; Modify the BEQ instruction at BDbeqmod2 so next time STA BDbeqmod2+1 ; it jumps to the first half of this routine LDA voice3lo1 ; Set SID registers $E and $F to the vibrato frequency STA SID+$F ; in (voice3lo1 voice3hi1), which sets the frequency LDA voice3hi1 ; for voice 3 to the second (i.e. the higher) frequency STA SID+$E ; that we set up for vibrato in BDlab7 JMP BDlab21 ; Jump to BDlab21 to clean up and return from the ; interrupt routine ; ****************************************************************************** ; ; Name: BDlab1 ; Type: Subroutine ; Category: Sound ; Summary: Apply vibrato before cleaning up and returning from the interrupt ; routine ; ; ****************************************************************************** .BDlab1 INC vibrato3 ; Increment the vibrato counter for voice 3 IF _GMA_RELEASE LDA #5 ; Set A = 5 so the period of the vibrato for voice 3 is ; five interrupts ELIF _SOURCE_DISK LDA #6 ; Set A = 6 so the period of the vibrato for voice 3 is ; six interrupts ENDIF CMP vibrato3 ; If the vibrato counter for voice 3 has reached the ; the value in A, then it is time to change the voice 3 ; frequency to implement vibrato, so jump to the address ; in the following BEQ instruction, which gets modified ; by the BDlab23 routine to oscillate between the two ; halves of the BDlab23 routine ; ; One half applies the lower vibrato frequency, and the ; other applies the higher vibrato frequency, so the ; effect is to flip between the two frequencies every A ; interrupts .BDbeqmod2 BEQ BDlab23 ; Jump to the BDlab23 routine to switch to the correct ; vibrato frequency and jump back to BDlab21 to clean up ; and return from the interrupt routine INC vibrato2 ; Increment the vibrato counter for voice 2 IF _GMA_RELEASE LDA #4 ; Set A = 5 so the period of the vibrato for voice 2 is ; four interrupts ELIF _SOURCE_DISK LDA #5 ; Set A = 5 so the period of the vibrato for voice 2 is ; five interrupts ENDIF CMP vibrato2 ; If the vibrato counter for voice 2 has reached the ; the value in A, then it is time to change the voice 2 ; frequency to implement vibrato, so jump to the address ; in the following BEQ instruction, which gets modified ; by the BDlab24 routine to oscillate between the two ; halves of the BDlab24 routine ; ; One half applies the lower vibrato frequency, and the ; other applies the higher vibrato frequency, so the ; effect is to flip between the two frequencies every A ; interrupts .BDbeqmod1 BEQ BDlab24 ; Jump to the BDlab24 routine to switch to the correct ; vibrato frequency and jump back to BDlab21 to clean up ; and return from the interrupt routine ; ****************************************************************************** ; ; Name: BDlab21 ; Type: Subroutine ; Category: Sound ; Summary: Clean up and return from the interrupt routine ; ; ****************************************************************************** .BDlab21 IF _GMA_RELEASE LDX counter ; If the rest counter is non-zero, jump to BDexitirq to CPX #0 ; return from the interrupt routine BNE BDexitirq ELIF _SOURCE_DISK LDX counter ; If the rest counter is not 2, jump to BDexitirq to CPX #2 ; return from the interrupt routine BNE BDexitirq ENDIF ; The rest counter is ticking down, which means no new ; notes are being played for the time being, so we need ; to silence all three voices LDX value1 ; Set SID register $4 (the voice control register for DEX ; voice 1) to value1 - 1 STX SID+$4 ; ; This changes bit 0 from a 1 to a 0, which turns off ; voice 1 and starts the release cycle (so this ; effectively terminates any music on voice 1) LDX value2 ; Set SID register $B (the voice control register for DEX ; voice 1) to value2 - 1 STX SID+$B ; ; This changes bit 0 from a 1 to a 0, which turns off ; voice 2 and starts the release cycle (so this ; effectively terminates any music on voice 2) LDX value3 ; Set SID register $12 (the voice control register for DEX ; voice 1) to value3 - 1 STX SID+$12 ; ; This changes bit 0 from a 1 to a 0, which turns off ; voice 3 and starts the release cycle (so this ; effectively terminates any music on voice 3) .BDexitirq RTS ; Return from the subroutine RTS ; This instruction has no effect as we have already ; returned from the subroutine ; ****************************************************************************** ; ; Name: BDJMPTBL ; Type: Variable ; Category: Sound ; Summary: A jump table containing addresses for processing music commands 1 ; through 15 (low bytes) ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDJMPTBL EQUB LO(BDRO1) EQUB LO(BDRO2) EQUB LO(BDRO3) EQUB LO(BDRO4) EQUB LO(BDRO5) EQUB LO(BDRO6) EQUB LO(BDRO7) EQUB LO(BDRO8) EQUB LO(BDRO9) EQUB LO(BDRO10) EQUB LO(BDRO11) EQUB LO(BDRO12) EQUB LO(BDRO13) EQUB LO(BDRO14) EQUB LO(BDRO15) ; ****************************************************************************** ; ; Name: BDJMPTBH ; Type: Variable ; Category: Sound ; Summary: A jump table containing addresses for processing music commands 1 ; through 15 (high bytes) ; Deep dive: Music in Commodore 64 Elite ; ; ****************************************************************************** .BDJMPTBH EQUB HI(BDRO1) EQUB HI(BDRO2) EQUB HI(BDRO3) EQUB HI(BDRO4) EQUB HI(BDRO5) EQUB HI(BDRO6) EQUB HI(BDRO7) EQUB HI(BDRO8) EQUB HI(BDRO9) EQUB HI(BDRO10) EQUB HI(BDRO11) EQUB HI(BDRO12) EQUB HI(BDRO13) EQUB HI(BDRO14) .musicstart EQUB HI(BDRO15) ; ****************************************************************************** ; ; Name: COMUDAT ; Type: Variable ; Category: Sound ; Summary: Music data from the C.COMUDAT file ; ; ****************************************************************************** IF _GMA_RELEASE INCBIN "1-source-files/music/gma/C.COMUDAT.bin" ELIF _SOURCE_DISK_BUILD INCBIN "1-source-files/music/source-disk-build/C.COMUDAT.bin" ELIF _SOURCE_DISK_FILES INCBIN "1-source-files/music/source-disk-files/C.COMUDAT.bin" ENDIF .THEME IF _SOURCE_DISK EQUB $28 ; C.THEME is not included in the encrypted HICODE binary ; in the source disk variant, unlike the GMA85 variant ELIF _GMA_RELEASE INCBIN "1-source-files/music/gma/C.THEME.bin" ENDIF ; ****************************************************************************** ; ; Name: F% ; Type: Variable ; Category: Utility routines ; Summary: Denotes the end of the main game code, from ELITE A to ELITE K ; ; ****************************************************************************** .F% SKIP 0 ASSERT F% < $CE00 ; Make sure that CODE2 doesn't spill over into the zero ; page swap space at $CE00 ; ****************************************************************************** ; ; Save ELTK.bin ; ; ****************************************************************************** PRINT "ELITE K" PRINT "Assembled at ", ~CODE_K% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_K%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_K% PRINT "S.ELTK ", ~CODE_K%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_J% SAVE "3-assembled-output/ELTK.bin", CODE_K%, P%, LOAD% PRINT "Addresses for the scramble routines in elite-checksum.py" PRINT "B% = ", ~CODE% PRINT "G% = ", ~G% PRINT "NA2% = ", ~NA2%