Skip to navigation

Elite on the BBC Micro and NES

Bank 0 (Part 5 of 5)

[NES version]

Name: ShowStartScreen [Show more] Type: Subroutine Category: Start and end Summary: Show the start screen and start the game
Context: See this subroutine on its own page References: This subroutine is called as follows: * ResetToStartScreen calls ShowStartScreen
.ShowStartScreen LDA #$FF ; Set soundVibrato = $FF $80 $1B $34 to set the seeds STA soundVibrato ; for the randomised vibrato that's applied to sound LDA #$80 ; effects STA soundVibrato+1 LDA #$1B STA soundVibrato+2 LDA #$34 STA soundVibrato+3 JSR ResetMusic ; Reset the current tune to 0 and stop the music JSR JAMESON_b6 ; Copy the default "JAMESON" commander to the buffer at ; currentSlot (though this isn't actually used anywhere) JSR ResetOptions ; Reset the game options to their default values LDA #1 ; Set the font style to print in the normal font STA fontStyle LDX #$FF ; Set the old view type in QQ11a to $FF (Segue screen STX QQ11a ; from Title screen to Demo) TXS ; Set the stack pointer to $01FF, which is the standard ; location for the 6502 stack, so this instruction ; effectively resets the stack JSR RESET ; Call RESET to initialise most of the game variables JSR ChooseLanguage_b6 ; Show the Start screen and process the language choice ; Fall through into DEATH2 to show the title screen and ; start the game
Name: DEATH2 [Show more] Type: Subroutine Category: Start and end Summary: Reset most of the game and restart from the title screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH calls DEATH2 * DEATH2_b0 calls DEATH2

This routine is called following death, and when the game is quit via the pause menu. This routine does a similar job to the routine of the same name in the BBC Master version of Elite, but the code is significantly different.
.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 INX ; Set chartToShow = 0 so the chart button on the icon STX chartToShow ; bar shows the Short-range Chart when chosen JSR RES2 ; Reset a number of flight variables and workspaces LDA #5 ; Set the icon par pointer to button 5 (which is the JSR SetIconBarPointer ; sixth button of 12, just before the halfway point) JSR U% ; Call U% to clear the key logger JSR DrawTitleScreen ; Draw the title screen with the rotating ships, ; returning when a key is pressed LDA controller1Select ; If Select, Start, A and B are all pressed at the same AND controller1Start ; time on controller 1, jump to dead2 to skip the demo AND controller1A ; and show the credits scroll text instead AND controller1B BNE dead2 LDA controller1Select ; If Select is pressed on either controller, jump to ORA controller2Select ; dead3 to skip the demo and start the game straight BNE dead3 ; away ; If we get here then we start the combat demo LDA #0 ; Store 0 on the stack, so this can be retrieved below PHA ; to pass to ShowScrollText, so the demo gets run after ; the scroll text is shown JSR BR1 ; Reset a number of variables, ready to start a new game LDA #$FF ; Set the view type in QQ11 to $FF (Segue screen from STA QQ11 ; Title screen to Demo) LDA autoPlayDemo ; If autoPlayDemo is zero then the demo is not being BEQ dead1 ; auto-played, so jump to dead1 to skip the following ; instruction JSR SetupDemoUniverse ; The demo is running and is being auto-played by the ; computer, so call SetupDemoUniverse to set up the ; local bubble for the demo .dead1 JSR WaitForNMI ; Wait until the next NMI interrupt has passed (i.e. the ; next VBlank) LDA #4 ; Select and play the combat demo music (tune 4, JSR ChooseMusic_b6 ; "Assassin's Touch" followed by "Game Theme") LDA tuneSpeed ; Set tuneSpeed = tuneSpeed + 6 CLC ; ADC #6 ; This speeds up the music in the combat demo to make STA tuneSpeed ; things a bit more exciting PLA ; Set A to the value of A that we put on the stack above ; (i.e. set A = 0) JMP ShowScrollText_b6 ; Jump to ShowScrollText to show the scroll text and run ; the demo, returning from the subroutine using a tail ; call .dead2 ; If we get here then we show the credits scroll text JSR BR1 ; Reset a number of variables, ready to start a new game LDA #$FF ; Set the view type in QQ11 to $FF (Segue screen from STA QQ11 ; Title screen to Demo) JSR WaitForNMI ; Wait until the next NMI interrupt has passed (i.e. the ; next VBlank) LDA #4 ; Select and play the combat demo music (tune 4, JSR ChooseMusic_b6 ; "Assassin's Touch" followed by "Game Theme") LDA #2 ; Set A = 2 to pass to ShowScrollText, so the credits ; scroll text is shown instead of the demo introduction, ; and to skip the demo after the scroll text JMP ShowScrollText_b6 ; Jump to ShowScrollText to show the scroll text and ; skip the demo, returning from the subroutine using a ; tail call .dead3 ; If we get here then we start the game without playing ; the demo JSR FadeToBlack_b3 ; Fade the screen to black over the next four VBlanks ; Fall through into StartGame to reset the stack and go ; to the docking bay (i.e. show the Status Mode screen)
Name: StartGame [Show more] Type: Subroutine Category: Start and end Summary: Reset the stack and game variables, and start the game by going to the docking bay
Context: See this subroutine on its own page References: This subroutine is called as follows: * StartGame_b0 calls StartGame * WARP calls StartGame
.StartGame 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 BR1 ; Reset a number of variables, ready to start a new game ; Fall through into the BAY routine to go to the docking ; bay (i.e. show the Status Mode screen)
Name: BAY [Show more] Type: Subroutine Category: Status Summary: Go to the docking bay (i.e. show the Status Mode screen)
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIEF2 calls BAY * BRP calls BAY * DOENTRY calls BAY * TBRIEF calls BAY

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 JSR ClearScreen_b3 ; Clear the screen by zeroing patterns 66 to 255 in ; both pattern buffer, and clearing both nametable ; buffers to the background tile LDA #$FF ; Set QQ12 = $FF (the docked flag) to indicate that we STA QQ12 ; are docked LDA #3 ; Jump into the main loop at FRCE, setting the key JMP FRCE ; that's "pressed" to the Status Mode icon bar button ; so we show the Status Mode screen
Name: BR1 [Show more] Type: Subroutine Category: Start and end Summary: Reset a number of variables, ready to start a new game
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH2 calls BR1 * StartGame calls BR1
.BR1 JSR SetupPPUForIconBar ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 JSR ResetCommander_b6 ; Reset the current commander to the default "JAMESON" ; commander JSR ResetMusicAfterNMI ; Wait for the next NMI before resetting the current ; tune to 0 (no tune) and stopping the music 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 RTS ; Return from the subroutine
Name: ChangeToView [Show more] Type: Subroutine Category: Drawing the screen Summary: Clear the screen and set a new view type
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIEF calls ChangeToView * ChangeToView_b0 calls ChangeToView * LAUN calls ChangeToView * TITLE calls ChangeToView

Arguments: A The type of the new view
.ChangeToView JSR TT66 ; Clear the screen and set the view type in QQ11 to the ; value of A JSR CopyNameBuffer0To1 ; Copy the contents of nametable buffer 0 to nametable ; buffer JSR UpdateScreen ; Update the screen by sending data to the PPU, either ; immediately or during VBlank, depending on whether ; the screen is visible LDA #$00 ; Set the view type in QQ11 to $00 (Space view with no STA QQ11 ; font loaded) STA QQ11a ; Set the old view type in QQ11a to $00 (Space view with ; no fonts loaded) STA showIconBarPointer ; Set showIconBarPointer to 0 to indicate that we should ; hide the icon bar pointer LDA firstFreePattern ; Tell the NMI handler to send pattern entries from the STA firstPattern ; first free pattern onwards, so we don't waste time ; resending the static patterns we have already sent LDA #80 ; Tell the NMI handler to only clear nametable entries STA maxNameTileToClear ; up to tile 80 * 8 = 640 (i.e. up to the end of tile ; row 19) LDX #8 ; Tell the NMI handler to send nametable entries from STX firstNameTile ; tile 8 * 8 = 64 onwards (i.e. from the start of tile ; row 2) RTS ; Return from the subroutine
Name: TITLE [Show more] Type: Subroutine Category: Start and end Summary: Display a title screen with a rotating ship and prompt
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTitleScreen calls TITLE

Display the title screen, with a selection of rotating ships.
Arguments: 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: C flag If the A button, Start or Select was being pressed but has now been released, on either one of the controllers, then the C flag is set, otherwise it is clear
.TITLE STY distaway ; Store the ship distance in distaway STX TYPE ; Store the ship type in location TYPE JSR RESET ; Reset our ship so we can use it for the rotating ; title ship JSR U% ; Call U% to clear the key logger JSR SetupPPUForIconBar ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA #96 ; Set nosev_z hi = 96 (96 is the value of unity in the STA INWK+14 ; rotation vector) LDA #55 ; Set A = 55 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 .awe JSR HideFromScanner_b1 ; Hide the ship from the scanner 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 LDY #0 ; Set DELTA = 0 (i.e. ship speed = 0) STY DELTA LDA #$01 ; Clear the screen and set the view type in QQ11 to $01 JSR ChangeToView ; (Title screen) LDA #7 ; Set YP = 7 to use as the outer loop counter for the STA YP ; loop starting at TLL2 .titl1 LDA #25 ; Set XP = 25 to use as the inner loop counter for the STA XP ; loop starting at TLL2 .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 titl5 ; Call titl5 below as a subroutine to rotate and move ; the ship in space BCS titl3 ; If a button was been pressed during the ship drawing, ; then the C flag sill be set, so jump to titl3 to ; return from the subroutine with the C flag set to ; indicate a button press DEC XP ; Decrement the inner loop counter in XP BNE TLL2 ; Loop back to keep the ship rotating, until the inner ; loop counter is zero DEC YP ; Decrement the outer loop counter in YP BNE titl1 ; Loop back to keep the ship rotating, until the outer ; loop counter is zero .titl2 LDA INWK+7 ; If z_hi (the ship's distance) is 55 or greater, jump CMP #55 ; to titl4 to return from the subroutine with the C flag BCS titl4 ; clear, as the ship has now come towards us and has ; gone away again, all without any buttons being pressed INC INWK+7 ; Increment the ship's distance, to move the ship a bit ; further away from us JSR titl5 ; Call titl5 below as a subroutine to rotate and move ; the ship in space BCC titl2 ; If no button was pressed during the ship drawing, then ; the C flag will be clear, so loop back to titl2 to ; move the ship away from us ; If a button was pressed, then the C flag will be set, ; so we now return from the subroutine with the C flag ; set .titl3 SEC ; Set the C flag to indicate that a button has been ; pressed RTS ; Return from the subroutine .titl4 CLC ; Clear the C flag to indicate that a button has not ; been pressed RTS ; Return from the subroutine .titl5 ; We call this part of the code as a subroutine from ; above JSR MV30 ; Call MV30 at the end of part 2 of MVEIT, so we move ; the ship in space but without tidying the orientation ; vectors or applying tactics (neither of which are ; necessary on the title screen) 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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 JSR DrawShipInBitplane ; Flip the drawing bitplane and draw the current ship in ; the newly flipped bitplane INC MCNT ; Increment the main loop counter LDA controller1A ; If any of the A button, Start or Select are being ORA controller1Start ; pressed on controller 1, jump to tite1 to check the ORA controller1Select ; same buttons on controller 2, as these don't count as BMI tite1 ; a button press until they are released BNE tite3 ; If the result is non-zero, then at least one of the ; A button, Start or Select were being pressed but have ; now been released, so jump to tite3 to set the number ; of pilots to one (as the buttons are being pressed on ; controller 1), and return from the subroutine with ; the C flag set, to indicate the button press .tite1 LDA controller2A ; If any of the A button, Start or Select are being ORA controller2Start ; pressed on controller 2, jump to tite2 to return from ORA controller2Select ; the subroutine with the C flag clear, as these don't BMI tite2 ; count as a button press until they are released BNE tite4 ; If the result is non-zero, then at least one of the ; A button, Start or Select were being pressed but have ; now been released, so jump to tite4 to keep the number ; of pilots to two (as the buttons are being pressed on ; controller 2) to return from the subroutine with the ; C flag set, to indicate the button press .tite2 CLC ; Clear the C flag to indicate that a button has not ; been pressed RTS ; Return from the subroutine .tite3 LSR numberOfPilots ; Set numberOfPilots = 0 to configure the game for one ; pilot .tite4 SEC ; Set the C flag to indicate that a button has been ; pressed RTS ; Return from the subroutine
Name: ZERO [Show more] Type: Subroutine Category: Utility routines Summary: Reset the local bubble of universe and ship status
Context: See this subroutine on its own page References: This subroutine is called as follows: * RES2 calls ZERO * RESET calls ZERO

This resets the following workspaces to zero: * WP workspace variables from FRIN to de, which include the ship slots for the local bubble of universe, and various flight and ship status variables, including the MANY block
.ZERO JSR SetupPPUForIconBar ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #(de-FRIN+1) ; We're going to zero the WP 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 .ZEL STA FRIN-1,X ; Zero byte X-1 of FRIN to de DEX ; Decrement the loop counter BNE ZEL ; Loop back to zero the next variable until we have done ; them all from FRIN to FRIN+42 LDX #NTY ; We're now going to zero the NTY bytes in the MANY ; block, so set a counter in X for the correct number of ; bytes .ZEL2 STA MANY,X ; Zero the X-th byte of MANY DEX ; Decrement the loop counter BPL ZEL2 ; Loop back to zero the next variable until we have done ; them all from MANY to MANY+33 JSR SetupPPUForIconBar ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 RTS ; Return from the subroutine
Name: U% [Show more] Type: Subroutine Category: Controllers Summary: Clear the key logger
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH2 calls U% * TITLE calls U%

Returns: A A is set to 0
.U% LDX #6 ; We want to clear the 6 key logger locations from ; KY1 to KY6, so set a counter in X LDA #0 ; Set A to 0, as this means "key not pressed" in the ; key logger at KL STA iconBarKeyPress ; Reset the key logger entry for the icon bar button ; choice .DKL3 STA KL,X ; Store 0 in the X-th byte of the key logger DEX ; Decrement the counter BPL DKL3 ; Loop back for the next key, until we have cleared from ; KL to KL+6 (i.e. KY1 through KY6) RTS ; Return from the subroutine
Name: MAS1 [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Add an orientation vector coordinate to an INWK coordinate Deep dive: The space station safe zone
Context: See this subroutine on its own page References: This subroutine is called as follows: * SpawnSpaceStation calls MAS1

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 [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate a cap on the maximum distance to the planet or sun
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 14 of 16) calls MAS2 * Main flight loop (Part 15 of 16) calls MAS2 * CheckAltitude calls via m

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 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate A = x_hi^2 + y_hi^2 + z_hi^2 in the K% block
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckAltitude calls MAS3 * Main flight loop (Part 15 of 16) calls MAS3

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 A = x_hi^2 + y_hi^2 + z_hi^2 A = $FF if the calculation overflows a one-byte result
.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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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 sum 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: SpawnSpaceStation [Show more] Type: Subroutine Category: Universe Summary: Add a space station to the local bubble of universe if we are close enough to the station's orbit
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 14 of 16) calls SpawnSpaceStation
.SpawnSpaceStation ; 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 (100 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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; Call MAS1 with X = 0, Y = 9 to do the following: LDY #9 ; JSR MAS1 ; (x_sign x_hi x_lo) += (nosev_x_hi nosev_x_lo) * 2 ; ; A = |x_sign| BNE MA23S2 ; If A > 0, jump to MA23S2 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 MA23S2 ; If A > 0, jump to MA23S2 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 MA23S2 ; If A > 0, jump to MA23S2 to skip the following, as we ; are too far from the planet in the z-direction to ; bump into a space station LDA #100 ; Call FAROF2 to compare x, y and z with 100 * 2, which JSR FAROF2 ; will clear the C flag if the distance to the point is ; < 200 or set the C flag if it is >= 200 BCS MA23S2 ; Jump to MA23S2 if the distance to point (x, y, z) is ; >= 100 (i.e. we must be near enough to the planet to ; bump into a space station) JSR NWSPS ; Add a new space station to our local bubble of ; universe SEC ; Set the C flag to indicate that we have added the ; space station RTS ; Return from the subroutine .MA23S2 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 CLC ; Clear the C flag to indicate that we have not added ; the space station RTS ; Return from the subroutine
Name: SPS2 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate X = A / 16
Context: See this subroutine on its own page References: This subroutine is called as follows: * SP2 calls SPS2

Calculate the following, where A is a sign-magnitude 8-bit integer and the result is a signed 8-bit integer: X = A / 16
Returns: C flag The C flag is cleared
.SPS2 TAY ; Copy the argument A to Y, so we can check its sign ; below AND #%01111111 ; Clear the sign bit of the argument A LSR A ; Set A = A / 16 LSR A LSR A LSR A ADC #0 ; Round the result up to the nearest integer by adding ; the bit we just shifted off the right (which went into ; the C flag) CPY #%10000000 ; If Y is positive (i.e. the original argument was BCC LL163 ; positive), jump to LL163 EOR #$FF ; Negate A using two's complement ADC #0 .LL163 TAX ; Copy the result in A to X RTS ; Return from the subroutine
Name: SPS3 [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Copy a space coordinate from the K% block into K3
Context: See this subroutine on its own page References: This subroutine is called as follows: * SPS1 calls SPS3

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: SPS1 [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the vector to the planet and store it in XX15
Context: See this subroutine on its own page References: This subroutine is called as follows: * COMPAS calls SPS1 * Main flight loop (Part 9 of 16) calls SPS1 * TACTICS (Part 3 of 7) calls SPS1

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 [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Normalise the three-coordinate vector in K3
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOCKIT calls TAS2 * SPS4 calls TAS2 * TACTICS (Part 3 of 7) calls TAS2 * DOCKIT calls via TA2

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 two places: when drawing the compass, and when applying AI tactics to ships. 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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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 tast1 ; 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) .tast1 LSR K3+1 ; Shift all three high bytes right by one place to LSR K3+4 ; reduce the chances of overflow in the normalisation LSR K3+7 ; process .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 JMP NORM ; Now we have a signed 8-bit version of the vector K3 in ; XX15, so jump to NORM to normalise it, returning from ; the subroutine using a tail call
Name: WARP [Show more] Type: Subroutine Category: Flight Summary: Process the fast-forward button to end the demo, dock instantly or perform an in-system jump
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 3 of 16) calls WARP

This routine does a similar job to the routine of the same name in the BBC Master version of Elite, but the code is significantly different.
.WARP LDA demoInProgress ; If the demo is not in progress, jump to warp1 to skip BEQ warp1 ; the following ; If we get here then the demo is in progress, in which ; case the fast-forward icon ends the demo and starts ; the game JSR ResetShipStatus ; Reset the ship's speed, hyperspace counter, laser ; temperature, shields and energy banks JMP StartGame ; Jump to StartGame to reset the stack and go to the ; docking bay (i.e. show the Status Mode screen) .warp1 LDA auto ; If the docking computer is engaged (auto is non-zero) AND SSPR ; and we are inside the space station safe zone (SSPR BEQ warp2 ; is non-zero), then this sets A to be non-zero, so if ; this is not the case, jump to warp2 to skip the ; following ; If we get here then the docking computer is engaged ; and we are in the space station safe zone, in which ; case the fast-forward button docks us instantly JMP GOIN ; Go to the docking bay (i.e. show the ship hangar ; screen) and return from the subroutine with a tail ; call .warp2 JSR FastForwardJump ; Do an in-system (fast-forward) jump and run the ; distance checks BCS warp3 ; If the C flag is set then we are too close to the ; planet or sun for any more jumps, so jump to warp3 ; to stop jumping JSR FastForwardJump ; Do a second in-system (fast-forward) jump and run the ; distance checks BCS warp3 ; If the C flag is set then we are too close to the ; planet or sun for any more jumps, so jump to warp3 ; to stop jumping JSR FastForwardJump ; Do a third in-system (fast-forward) jump and run the ; distance checks BCS warp3 ; If the C flag is set then we are too close to the ; planet or sun for any more jumps, so jump to warp3 ; to stop jumping JSR WaitForNMI ; Wait until the next NMI interrupt has passed (i.e. the ; next VBlank) JSR InSystemJump ; Do a fourth in-system jump (fast-forward) without ; doing the distance checks .warp3 LDA #1 ; Set the main loop counter to 1, so the next iteration STA MCNT ; 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) JSR CheckAltitude ; Perform an altitude check with the planet, ending the ; game if we hit the ground LDA QQ11 ; If this is not the space view, jump to warp4 to skip BNE warp4 ; the updating of the space view and return from the ; subroutine LDX VIEW ; Set X to the current view (front, rear, left or right) DEC VIEW ; Decrement the view in VIEW so the call to LOOK1 thinks ; the view has changed, so it will update the screen JMP LOOK1 ; Jump to LOOK1 to initialise the view in X, returning ; from the subroutine using a tail call .warp4 RTS ; Return from the subroutine
Name: FastForwardJump [Show more] Type: Subroutine Category: Flight Summary: Perform an in-system jump
Context: See this subroutine on its own page References: This subroutine is called as follows: * WARP calls FastForwardJump

Returns: C flag The status at the end of the jump: * Clear if the jump ends with us being far enough away from the planet or sun to do another jump * Set if the jump ends with us being too close to the planet or sun to do another jump
.FastForwardJump JSR WaitForNMI ; Wait until the next NMI interrupt has passed (i.e. the ; next VBlank) JSR InSystemJump ; Call InSystemJump to do an in-system jump ; Fall through into CheckJumpSafety to work out if we ; are too close to the planet or sun to do another ; in-system jump, returning the result in the C flag ; accordingly
Name: CheckJumpSafety [Show more] Type: Subroutine Category: Flight Summary: Check whether we are far enough away from the planet and sun to be able to do an in-system (fast-forward) jump
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 2 of 16) calls CheckJumpSafety * Main flight loop (Part 2 of 16) calls via CheckJumpSafety+2

This routine checks how far away from the planet and sun we would be if we were to do an in-system jump. If we are too close to either object after doing a jump, then the C flag is set, otherwise it is clear. The distance check is only done in the forward direction; if the planet or sun is behind us, then it is deemed safe to do a jump. By default, this routine checks distances against a value of 64 (which is the distance of an in-system jump). Arbitrary distances can be checked via the entry point at CheckJumpSafety+2. The algorithm actually calculates the distance as 0.5 * |x y z|, using an approximation that that estimates the length within 8% of the correct value, and without having to do any multiplication or take any square roots. If h is the longest of x, y, z, and a and b are the other two sides, then the algorithm is as follows: 0.5 * |(x y z)| ~= (5 * a + 5 * b + 16 * h) / 32 which we calculate like this: 5/32 * a + 5/32 * b + 1/2 * h Calculating half the distance to the point (i.e. 0.5 * |x y z|) ensures that the result fits into one byte. The distance to compare with is also halved.
Returns: C flag Results of the safety check: * Clear if we are not close to the planet or sun and can do an in-system jump * Set if we are too close to the planet or sun to do an in-system jump
Other entry points: CheckJumpSafety+2 Check the distances against the value of 0.5 * A
.CheckJumpSafety LDA #128 ; Set A = 128, to use as the default distance to check ; our proximity against LSR A ; Set T = A / 2 STA T ; ; So the value of T is set as follows: ; ; * T = 64 if we call the routine via CheckJumpSafety ; ; * T = A / 2 if we call the routine via the entry ; point at CheckJumpSafety+2 ; ; T is the value that we check the distances against ; to determine whether we are too close to the planet ; or sun LDY #0 ; Set Y as the offset in K% to the first ship data ; block, i.e. the planet JSR cdis1 ; Call cdis1 below to check our distance from the planet BCS cdis7 ; If the C flag is set then we are deemed close to the ; planet, so there is no need to check the sun, so jump ; to cdis7 to return from the subroutine with the C flag ; set LDA SSPR ; If SSPR is non-zero then we are inside the space BNE cdis7 ; station's safe zone, so we can't be too close to the ; sun, so jump to cdis7 return from the subroutine with ; the C flag clear (we know this is the case as we just ; passed through a BCS) ; If we get here then we have checked the distance to ; the planet and we are not close to it, so now we check ; our distance from the sun LDY #NIK% ; Set Y as the offset in K% to the second ship data ; block, i.e. the sun (this can't be the space station ; as we know we aren't in the safe zone) .cdis1 ; In the following, K%+Y points to the ship data block ; for the object we are measuring the distance to (i.e. ; the planet or sun) ; ; To make things easier to follow, let's refer to this ; object as the planet, with the planet's centre being ; at (x, y, z), where each coordinate is of the form ; (x_sign x_hi x_lo) LDA K%+2,Y ; If either of x_sign or y_sign are non-zero (ignoring ORA K%+5,Y ; the sign in bit 7), then jump to cdis5 to return with ASL A ; the C flag clear, as the planet is a long way away BNE cdis5 ; to the sides or above/below us LDA K%+8,Y ; If z_sign is negative (i.e. bit 7 is set), or z_sign LSR A ; is positive and z_sign > 1, then jump to cdis5 to BNE cdis5 ; return with the C flag clear, as the planet is either ; behind us, or it's a long way in front of us ; The above sets the C flag to bit 0 of z_sign LDA K%+7,Y ; Set A = (z_sign z_hi) / 2 - 32 ROR A ; SEC ; This result will fit into one byte because we know SBC #32 ; bits 1 to 7 of z_sign are clear ; ; As we know the rest of z_sign is empty, let's just ; simplify this to: ; ; A = (z_hi / 2) - 32 ; = (z_hi - 64) / 2 BCS cdis2 ; If the above subtraction didn't underflow, jump to ; cdis2 to skip the following EOR #$FF ; The subtraction underflowed so A is negative, so make ADC #1 ; A positive using two's complement (which will work as ; we know the C flag is clear as we just passed through ; a BCS) ; ; We therefore have A = |(z_hi - 64) / 2| .cdis2 STA K+2 ; Set K+2 = |(z_hi - 64) / 2| LDA K%+1,Y ; Set K = x_hi / 2 LSR A STA K LDA K%+4,Y ; Set K+1 = y_hi / 2 LSR A ; STA K+1 ; This also sets A = K+1 ; From this point on we are only working with the high ; bytes, so to make things easier to follow, let's just ; refer to x_hi, y_hi and z_hi as x, y and z, so: ; ; K = x / 2 ; K+1 = y / 2 ; K+2 = (z - 64) / 2 ; ; The following algorithm is the same as the FAROF2 ; routine, so this measures the distance from our ship ; to the point (x, y, z - 64), which is where (x, y, z) ; will be if we jump forward by a distance of z_hi = 64 ; ; In other words, the following checks the distance from ; our ship to the planet if we were to do an in-system ; jump forwards ; ; Note that it actually calculates half the distance to ; the point (i.e. 0.5 * |x y z|) as this will ensure the ; result fits into one byte CMP K ; If A >= K, jump to cdis3 to skip the next instruction BCS cdis3 LDA K ; Set A = K, so A = max(K, K+1) .cdis3 CMP K+2 ; If A >= K+2, jump to cdis4 to skip the next BCS cdis4 ; instruction LDA K+2 ; Set A = K+2, so A = max(A, K+2) ; = max(K, K+1, K+2) .cdis4 STA SC ; Set SC = A ; = max(K, K+1, K+2) ; = max(x / 2, y / 2, z / 2) ; = max(x, y, z) / 2 LDA K ; Set SC+1 = (K + K+1 + K+2 - SC) / 4 CLC ; = (x/2 + y/2 + z/2 - max(x, y, z) / 2) / 4 ADC K+1 ; = (x + y + z - max(x, y, z)) / 8 ADC K+2 ; SEC ; There is a risk that the addition will overflow here, SBC SC ; but presumably this isn't an issue LSR A LSR A STA SC+1 LSR A ; Set A = (SC+1 / 4) + SC+1 + SC LSR A ; = 5/4 * SC+1 + SC ADC SC+1 ; = 5 * (x + y + z - max(x, y, z)) / (8 * 4) ADC SC ; + max(x, y, z) / 2 ; ; If h is the longest of x, y, z, and a and b are the ; other two sides, then we have: ; ; max(x, y, z) = h ; ; x + y + z - max(x, y, z) = a + b + h - h ; = a + b ; ; So: ; ; A = 5 * (a + b) / (8 * 4) + h / 2 ; = 5/32 * a + 5/32 * b + 1/2 * h ; ; This estimates half the length of the (x, y, z) ; vector, i.e. 0.5 * |x y z|, using an approximation ; that estimates the length within 8% of the correct ; value, and without having to do any multiplication ; or take any square roots CMP T ; If A < T, C will be clear, otherwise C will be set ; ; So the C flag is clear if |x y z| < argument A ; set if |x y z| >= argument A BCC cdis6 ; If the C flag is clear then |x y z| < argument A, ; which means we are close to the planet, so jump to ; cdis6 to return with the C flag set to indicate this ; Otherwise |x y z| >= argument A, which means we are ; not close to the planet, so fall through into cdis5 ; to return with the C flag clear to indicate this .cdis5 CLC ; Set the C flag to indicate that we are not close to ; the planet and can do an in-system jump RTS ; Return from the subroutine .cdis6 SEC ; Set the C flag to indicate that we are too close to ; the planet to do an in-system jump .cdis7 RTS ; Return from the subroutine
Name: InSystemJump [Show more] Type: Subroutine Category: Flight Summary: Perform an in-system (fast-forward) jump
Context: See this subroutine on its own page References: This subroutine is called as follows: * FastForwardJump calls InSystemJump * WARP calls InSystemJump

This routine performs an in-system jump by subtracting 64 from z_hi for the planet and sun, and removing all other ships from the bubble. This is the same as our ship moving forwards in space by z_hi = 64, and leaving all the other ships behind.
.InSystemJump LDY #32 ; We start by charging the shields and energy banks 32 ; times, so set a loop counter in Y .jump1 JSR ChargeShields ; Charge the shields and energy banks DEY ; Decrement the loop counter BNE jump1 ; Loop back to charge the shields until we have done it ; 32 times ; We now move the sun and planet backwards in space and ; remove everything else from the ship slots, to make it ; appear as if we have jumped forward, leaving ; everything else behind LDX #0 ; We are about to loop through the ship slots, moving ; everything backwards so we appear to jump forwards in ; space, so set X = 0 to use as the slot number STX GNTMP ; Set GNTMP = 0 to cool the lasers down completely .jump2 STX XSAV ; Store the slot number in XSAV so we can retrieve it ; below LDA FRIN,X ; Load the ship type for the X-th slot BEQ jump4 ; If the slot contains 0 then it is empty and we have ; processed all the slots (as they are always shuffled ; down in the main loop to close up and gaps), so jump ; to jump4 as we are done BMI jump3 ; If the slot contains a ship type with bit 7 set, then ; it contains the planet or the sun, so jump down to ; jump3 to move the planet or sun in space ; If we get here then this is not the planet or sun, so ; we now remove this ship from our local bubble of ; universe JSR GINF ; Call GINF to get the address of the data block for ; ship slot X and store it in INF JSR RemoveShip ; Fetch the ship's data block and remove the ship from ; our local bubble of universe LDX XSAV ; Set X to the slot counter that we stored in XSAV above JMP jump2 ; Loop back to process the next slot .jump3 JSR GINF ; Call GINF to get the address of the data block for ; ship slot X and store it in INF LDA #$80 ; Set (S R) = -64 STA S ; LSR A ; This is a sign-magnitude number, with bit 7 of S set STA R ; and R = 128 / 2 = 64 LDY #7 ; Set P = z_hi from the ship's data block LDA (INF),Y STA P INY ; Set A = z_sign from the ship's data block LDA (INF),Y JSR ADD ; Set (A X) = (A P) + (S R) ; = (z_sign z_hi) - 64 STA (INF),Y ; Store the result in (z_sign z_hi) in the ship's data DEY ; block, so the object moves backwards by a distance of TXA ; z_hi = 64 (which is the distance of an in-system jump) STA (INF),Y LDX XSAV ; Set X to the slot counter that we stored in XSAV above INX ; Increment X to point to the next ship slot BNE jump2 ; Loop back to process the next slot (this BNE is ; effectively a JMP as we will exit the above loop well ; before X wraps around to 0 .jump4 RTS ; Return from the subroutine
Name: DOKEY [Show more] Type: Subroutine Category: Controllers Summary: Scan for the seven primary flight controls and apply the docking computer manoeuvring code Deep dive: The key logger The docking computer
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT17 calls DOKEY
.DOKEY JSR SetKeyLogger_b6 ; Populate the key logger table with the controller ; button presses and return the button number in X ; if an icon bar button has been chosen LDA auto ; If auto is non-zero, then the docking computer is BNE doky3 ; currently activated, so jump to doky3 to apply the ; docking computer manoeuvring code below .doky1 LDX iconBarKeyPress ; If the icon bar key logger entry in iconBarKeyPress CPX #64 ; is not 64, then jump to doky2 to skip the following BNE doky2 ; (interestingly, iconBarKeyPress can never contain ; this value, as none of the keys in the iconBarButtons ; table have this value, and the value for the Start ; button is 80) JMP PauseGame_b6 ; Pause the game and process choices from the pause menu ; until the game is unpaused by another press of Start, ; returning from the subroutine using a tail call .doky2 RTS ; Return from the subroutine .doky3 LDA SSPR ; If we are inside the space station safe zone, jump to BNE doky4 ; doky4 to run the docking computer manoeuvring code STA auto ; Otherwise set auto to 0 to disable the docking ; computer, so we can't engage it outside of the safe ; zone JSR ResetMusicAfterNMI ; Wait for the next NMI before resetting the current ; tune to 0 (no tune) and stopping the music JMP doky1 ; Loop back to doky1 to check the icon bar key logger ; entry and return from the subroutine .doky4 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 #0 ; Set X = 0, so we "press" KY1 below (the "slow down" ; key combination of the B and down buttons) 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 #1 ; Set X = 1, so we "press" KY+1, i.e. KY2, with the ; next instruction (speed up) STA KL,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, i.e. the B and ; down buttons, to slow down) or positive (in which case ; we "press" KY2, i.e. the B and up buttons, 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 #2 ; Set X = 2, so we "press" KL+2, i.e. KY3 below (left ; button, 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 #3 ; The C flag is set, i.e. the direction of the updated ; roll counter is negative, so set X to 3 so we ; "press" KY+3. i.e. KY4, below (right button, 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 KL,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 #4 ; Set X = 4, so we "press" KY+4, i.e. KY5, below ; (down button, 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 #5 ; Set X = 5, so we "press" KY+5, i.e. KY6, with the next ; instruction (up button, increase pitch, so the nose ; dives) STA KL,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, the down ; button, to decrease the pitch, pulling the nose up) or ; positive (in which case we "press" KY6, the up button, ; 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 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 left button is not being pressed, skip the next BEQ P%+5 ; instruction JSR BUMP2 ; The left button is being pressed, so call the BUMP2 ; routine to increase the roll rate in X by A LDY KY4 ; If the right button is not being pressed, skip the BEQ P%+5 ; next instruction JSR REDU2 ; The right button 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 LDA #14 ; Set A to 15, which is the amount we want to alter the ; roll rate by if the pitch keys are being pressed LDX JSTY ; Set X = JSTY, the current pitch rate (as shown in the ; DC indicator on the dashboard) LDY KY5 ; If the down button is not being pressed, skip the next BEQ P%+5 ; instruction JSR REDU2 ; The down button 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 up button is not being pressed, skip the next BEQ P%+5 ; instruction JSR BUMP2 ; The up button 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 auto ; If auto is non-zero, then the docking computer is BNE doky6 ; currently activated, so jump up to doky1 via doky6 to ; check the icon bar key logger entry and return from ; the subroutine LDX #128 ; Set X = 128, which indicates no change in pitch when ; stored in JSTX (i.e. the centre of the pitch ; indicator) LDA KY3 ; If either of the left or right buttons are being ORA KY4 ; pressed, jump to doky5 to skip the following BNE doky5 ; instruction, so pressing buttons on the controller ; overrides the docking computer STX JSTX ; Store the updated roll rate in JSTX .doky5 LDA KY5 ; If either of the up or down buttons are being ORA KY6 ; pressed, jump to doky6 to skip the following BNE doky6 ; instruction, so pressing buttons on the controller ; overrides the docking computer STX JSTY ; Store the updated roll rate in JSTY .doky6 JMP doky1 ; Loop back to doky1 to check the icon bar key logger ; entry and return from the subroutine
Name: PrintMessage [Show more] Type: Subroutine Category: Text Summary: Print a message in the middle of the screen (used for "GAME OVER" and demo missile messages only)
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH calls PrintMessage * FRMIS calls PrintMessage

Arguments: A The text token to be printed Y The length of time to leave the message on-screen
.PrintMessage PHA ; Store A on the stack so we can restore it after the ; following STY DLY ; Set the message delay in DLY to Y LDA #%11000000 ; Set the DTW4 flag to %11000000 (justify text, buffer STA DTW4 ; entire token including carriage returns) LDA #0 ; Set DTW5 = 0, to reset the size of the message in the STA DTW5 ; text buffer at BUF PLA ; Restore A from the stack JSR ex_b2 ; Print the recursive token in A JMP StoreMessage ; Jump to StoreMessage to copy the message from the ; justified text buffer in BUF into the message buffer ; at messageBuffer, returning from the subroutine using ; a tail call
Name: MESS [Show more] Type: Subroutine Category: Flight Summary: Display an in-flight message
Context: See this subroutine on its own page References: This subroutine is called as follows: * EXNO2 calls MESS * FR1 calls MESS * KILLSHP calls MESS * Main flight loop (Part 8 of 16) calls MESS * Main flight loop (Part 12 of 16) calls MESS * Main flight loop (Part 15 of 16) calls MESS * ou3 calls MESS * OUCH calls MESS * SFRMIS calls MESS * TT102 calls MESS

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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDY #10 ; Set the message delay in DLY to 10 STY DLY LDA #%11000000 ; Set the DTW4 flag to %11000000 (justify text, buffer STA DTW4 ; entire token including carriage returns) LDA #0 ; Set DTW5 = 0, which sets the size of the justified STA DTW5 ; text buffer at BUF to zero PLA ; Restore A from the stack CMP #250 ; If this is not token 250 (the hyperspace countdown), BNE mess1 ; jump to mess1 to print the token in A ; This is token 250, so now we print the hyperspace ; countdown LDA #0 ; Set QQ17 = 0 to switch to ALL CAPS STA QQ17 LDA #189 ; Print recursive token 29 ("HYPERSPACE ") JSR TT27_b2 LDA #'-' ; Print a hyphen JSR TT27_b2 JSR TT162 ; Print a space SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 JSR SetSelectedSeeds ; Set the seeds for the selected system in QQ15 to the ; seeds in the safehouse LDA #3 ; Set A = 3 so we print the hyperspace countdown with ; three digits CLC ; Clear the C flag so we print the hyperspace countdown ; without a decimal point LDX QQ22+1 ; Set (Y X) = QQ22+1, which contains the number that's LDY #0 ; shown on-screen during hyperspace countdown JSR TT11 ; Print the hyperspace countdown with 3 digits and no ; decimal point JMP mes9+3 ; Jump to mes9+3 to skip the following and not print ; the message in A, as we have already printed it .mess1 PHA ; Store A on the stack so we can restore it after the ; following LDA #0 ; Set QQ17 = 0 to switch to ALL CAPS STA QQ17 PLA ; Restore A from the stack ; Fall through into mes9 to print the token in A
Name: mes9 [Show more] Type: Subroutine Category: Flight Summary: Print a text token, possibly followed by " DESTROYED"
Context: See this subroutine on its own page References: This subroutine is called as follows: * MESS calls via mes9+3

Print a text token, followed by " DESTROYED" if the destruction flag is set (for when a piece of equipment is destroyed).
Other entry points: mes9+3 Don't print the text token, just print " DESTROYED" where applicable
.mes9 JSR TT27_b2 ; Call TT27 to print the text token in A LDA de ; If de is zero, then jump to StoreMessage to skip the BEQ StoreMessage ; following, as we don't need to print " DESTROYED" LDA #253 ; Print recursive token 93 (" DESTROYED") JSR TT27_b2 ; Fall through into StoreMessage to copy the message ; from the justified text buffer in BUF into the ; message buffer at messageBuffer
Name: StoreMessage [Show more] Type: Subroutine Category: Text Summary: Copy a message from the justified text buffer at BUF into the message buffer
Context: See this subroutine on its own page References: This subroutine is called as follows: * mes9 calls StoreMessage * PrintMessage calls StoreMessage
.StoreMessage LDA #32 ; Set A = 32 - DTW5 SEC ; SBC DTW5 ; Where DTW5 is the size of the justified text buffer at ; BUF, so A contains the number of characters remaining ; if we print the message buffer on one line (as each ; line contains 32 characters) BCS smes1 ; If the subtraction didn't underflow, then the message ; in the message buffer will fit on one line, so jump to ; smes1 with the remaining number of characters in A ; The subtraction underflowed, so the message will not ; fit on one line ; ; In this case we just print as many characters as we ; can and truncate the message at the end of the line LDA #31 ; Set the size of the message buffer in DTW5 to 31, STA DTW5 ; which is the maximum size of a one-line message LDA #2 ; Set A = 2 so the message will be printed in column 1 ; on the left of the screen .smes1 ; When we get here, A contains the number of characters ; remaining if we were to print the message on one line ; of the screen LSR A ; Set A = A / 2 ; ; So A now contains half the amount of free space left ; if we print the message on one line, which is the ; amount of space on each side of the message when it is ; centred on the line ; ; In other words, this is the column number where we ; need to print our message for it to be centred ; on-screen 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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX DTW5 ; Set the size of the message in the message buffer to STX messageLength ; the size of the justified text buffer, as we are about ; to copy from one to the other INX ; Set X as a character counter so we can loop through ; message and copy it one character at a time (we ; increment it so X is at least 1, to make the following ; loop work) .smes2 LDA BUF-1,X ; Copy the character number X - 1 from BUF into STA messageBuffer-1,X ; messageBuffer DEX ; Decrement the character counter BNE smes2 ; Loop back until we have copied all X characters STX de ; Zero de, the flag that appends " DESTROYED" to the ; end of the next text token, so that it doesn't append ; it to the next message SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 ; Fall through into DisableJustifyText to reset DTW4 and ; DTW5 to turn off justified text and reset the ; justified text buffer
Name: DisableJustifyText [Show more] Type: Subroutine Category: Text Summary: Turn off justified text and reset the justified text buffer
Context: See this subroutine on its own page References: This subroutine is called as follows: * HME2 calls DisableJustifyText * PrintFlightMessage calls via RTS5

Other entry points: RTS5 Contains an RTS
.DisableJustifyText LDA #0 ; Set DTW4 = %00000000 (do not justify text, print STA DTW4 ; buffer on carriage return) STA DTW5 ; Set DTW5 = 0, to reset the size of the message in the ; text buffer at BUF .RTS5 RTS ; Return from the subroutine
Name: PrintFlightMessage [Show more] Type: Subroutine Category: Text Summary: Print an in-flight message
Context: See this subroutine on its own page References: This subroutine is called as follows: * FlightLoop4To16 calls PrintFlightMessage
.PrintFlightMessage LDA messYC ; Set A to the current row for in-flight messages LDX QQ11 ; If this is the space view, jump to fmes1 to skip the BEQ fmes1 ; following and leave A with this value, so we print the ; in-flight message on the row specified in messYC JSR CLYNS+8 ; Clear the bottom two text rows of the visible screen, ; and move the text cursor to column 1 on row 21, i.e. ; the start of the top row of the two bottom rows, but ; without resetting the in-flight message timer LDA #23 ; Set A to 23, so we print the in-flight message on row ; 23 for all views other than the space view .fmes1 STA YC ; Move the text cursor to the row in A LDX #0 ; Set QQ17 = 0 to switch to ALL CAPS STX QQ17 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA messXC ; Move the text cursor to column messXC, which we set STA XC ; to the text column of the current in-flight message ; when we called MESS to display it LDA messXC ; This appears to be an unnecessary duplicate of the STA XC ; above LDY #0 ; We now work through the message one character at a ; time, so set a character counter in Y .fmes2 LDA messageBuffer,Y ; Fetch the Y-th character from the message buffer JSR CHPR_b2 ; Print the character INY ; Increment the character counter in Y CPY messageLength ; Loop back until we have printed all the characters in BNE fmes2 ; the buffer, whose size is in messageLength LDA QQ11 ; If this is the space view, jump to RTS5 to return from BEQ RTS5 ; the subroutine, as the NMI handler will take care of ; updating the screen when we next flip bitplanes JMP DrawMessageInNMI ; Configure the NMI to display the message that we just ; printed, returning from the subroutine using a tail ; call
Name: OUCH [Show more] Type: Subroutine Category: Flight Summary: Potentially lose cargo or equipment following damage
Context: See this subroutine on its own page References: This subroutine is called as follows: * OOPS calls OUCH * ou2 calls via ouch1

Our shields are dead and we are taking damage, so there is a small chance of losing cargo or equipment.
Other entry points: ouch1 Print the token in A as an in-flight message
.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 .ouch1 JSR MESS ; Print recursive token A ("ENERGY BOMB", "ENERGY UNIT" ; or "DOCKING COMPUTERS") as an in-flight message, ; followed by " DESTROYED" JMP UpdateIconBar_b3 ; Update the icon bar to remove icons for any equipment ; that has been destroyed, returning from the subroutine ; using a tail call .out RTS ; Return from the subroutine
Name: ou2 [Show more] Type: Subroutine Category: Flight Summary: Display "E.C.M.SYSTEM DESTROYED" as an in-flight message
Context: See this subroutine on its own page References: This subroutine is called as follows: * OUCH calls ou2
.ou2 LDA #108 ; Set A to recursive token 108 ("E.C.M.SYSTEM") BNE ouch1 ; Jump up to ouch1 to print recursive token A as an ; in-flight message, followed by " DESTROYED", and ; return from the subroutine using a tail call (this ; BNE is effectively a JMP as A is never zero)
Name: ou3 [Show more] Type: Subroutine Category: Flight Summary: Display "FUEL SCOOPS DESTROYED" as an in-flight message
Context: See this subroutine on its own page References: This subroutine is called as follows: * OUCH calls ou3
.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: QQ23 [Show more] Type: Variable Category: Market Summary: Market prices table
Context: See this variable on its own page References: This variable is used as follows: * GVL uses QQ23 * TT151 uses QQ23 * TT210 uses QQ23

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, we'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: * 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 = Robot Slaves (Slaves in original) ITEM 83, -5, 't', 251, %00001111 ; 4 = Beverages (Liquor/Wines in original) ITEM 196, 8, 't', 54, %00000011 ; 5 = Luxuries ITEM 235, 29, 't', 8, %01111000 ; 6 = Rare Species (Narcotics in original) 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 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: PAS1 [Show more] Type: Subroutine Category: Missions Summary: Display a rotating ship at space coordinates (0, 100, 256) and scan the controllers
Context: See this subroutine on its own page References: This subroutine is called as follows: * PAS1_b0 calls PAS1
.PAS1 LDA #100 ; Set y_lo = 100 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 DrawShipInBitplane ; Flip the drawing bitplane and draw the current ship in ; the newly flipped bitplane INC MCNT ; Increment the main loop counter JMP MVEIT ; Call MVEIT to move and rotate the ship in space, ; returning from the subroutine using a tail call JMP SetKeyLogger_b6 ; This instruction is never reached and has no effect ; (it would populate the key logger table with the ; controller button presses)
Name: MVEIT (Part 1 of 9) [Show more] 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
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIEF calls MVEIT * ESCAPE calls MVEIT * Main flight loop (Part 6 of 16) calls MVEIT * PAS1 calls MVEIT

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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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_b1 ; 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) [Show more] Type: Subroutine Category: Moving Summary: Move current ship: Call tactics routine, remove ship from scanner Deep dive: Scheduling tasks with the main loop counter
Context: See this subroutine on its own page References: This subroutine is called as follows: * MVEIT (Part 1 of 9) calls via MV30 * TITLE calls via MV30

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
Other entry points: MV30 Move the ship in space but without tidying the orientation vectors or applying tactics
.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 if this is a ; ship or missile it is dumb and has no AI, and if this ; is the space station it is not hostile, so in both ; cases skip the following as it 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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 JSR TACTICS ; Call TACTICS to apply AI tactics to this ship .MV30
Name: MVEIT (Part 3 of 9) [Show more] Type: Subroutine Category: Moving Summary: Move current ship: Move ship forward according to its speed
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

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
SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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) [Show more] Type: Subroutine Category: Moving Summary: Move current ship: Apply acceleration to ship's speed as a one-off
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

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 STA INWK+27 ; Store the updated speed in byte #27 LDY #15 ; Set A to byte #15 from the current ship blueprint, JSR GetShipBlueprint ; which contains the ship's maximum speed CMP INWK+27 ; If A >= the ship's current speed, skip the following BCS P%+4 ; instruction as the speed is already in the correct ; range STA INWK+27 ; Otherwise store the maximum 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) [Show more] Type: Subroutine Category: Moving Summary: Move current ship: Rotate ship's location by our pitch and roll Deep dive: Rotating the universe
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

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. See below for a deeper explanation of this routine
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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 ; 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) [Show more] Type: Subroutine Category: Moving Summary: Move current ship: Move the ship in space according to our speed
Context: See this subroutine on its own page References: This subroutine is called as follows: * MV40 calls via MV45

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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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) [Show more] Type: Subroutine Category: Moving Summary: Move current ship: Rotate ship's orientation vectors by pitch/roll Deep dive: Orientation vectors Pitching and rolling
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

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) [Show more] 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
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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) [Show more] Type: Subroutine Category: Moving Summary: Move current ship: Redraw on scanner, if it hasn't been destroyed
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

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 ; Set bit 4 to keep the ship visible on the scanner ORA #%00010000 STA INWK+31 JMP SCAN_b1 ; Display the ship on the scanner, returning from the ; subroutine using a tail call
Name: MVT1 [Show more] Type: Subroutine Category: Moving Summary: Calculate (x_sign x_hi x_lo) = (x_sign x_hi x_lo) + (A R)
Context: See this subroutine on its own page References: This subroutine is called as follows: * MVEIT (Part 6 of 9) calls MVT1 * SFS2 calls MVT1 * MVEIT (Part 3 of 9) calls via MVT1-2

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 [Show more] Type: Subroutine Category: Moving Summary: Apply pitch and roll to an orientation vector Deep dive: Orientation vectors Pitching and rolling
Context: See this subroutine on its own page References: This subroutine is called as follows: * MVEIT (Part 7 of 9) calls MVS4

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, there's a detailed explanation of all this in the deep dive on "Pitching and rolling".
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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 RTS ; Return from the subroutine
Name: MVT6 [Show more] Type: Subroutine Category: Moving Summary: Calculate (A P+2 P+1) = (x_sign x_hi x_lo) + (A P+2 P+1)
Context: See this subroutine on its own page References: This subroutine is called as follows: * MVEIT (Part 5 of 9) calls MVT6

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 [Show more] Type: Subroutine Category: Moving Summary: Rotate the planet or sun's location in space by the amount of pitch and roll of our ship
Context: See this subroutine on its own page References: This subroutine is called as follows: * MVEIT (Part 2 of 9) calls MV40

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 See the deep dive on "Rotating the universe" for more details on the above.
.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: PLUT [Show more] Type: Subroutine Category: Flight Summary: Flip the coordinate axes for the four different views Deep dive: Flipping axes between space views
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 11 of 16) calls PLUT

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 [Show more] Type: Subroutine Category: Flight Summary: Initialise the space view
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT102 calls LOOK1 * TT110 calls LOOK1 * WARP calls LOOK1

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 JSR SendSpaceViewToPPU ; Set a new space view, clear the screen, copy the ; nametable buffers and configure the PPU for the new ; view 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 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) JSR SetSpaceViewInNMI ; Change the current space view to X and configure the ; NMI to send both bitplanes to the PPU during VBlank JSR FLIP ; Swap the x- and y-coordinates of all the stardust ; particles and redraw the stardust field JMP WaitForNMI ; Wait until the next NMI interrupt has passed (i.e. the ; next VBlank) and return from the subroutine using a ; tail call
Name: FLIP [Show more] Type: Subroutine Category: Stardust Summary: Reflect the stardust particles in the screen diagonal
Context: See this subroutine on its own page References: This subroutine is called as follows: * LOOK1 calls FLIP

Swap the x- and y-coordinates of all the stardust 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 LDY NOSTM ; Set Y to the current number of stardust particles, so ; we can use it as a counter through all the stardust .FLL1 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 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 SY,Y ; the particle's y-coordinate TXA ; Copy the Y-th particle's original y-coordinate into STA SX,Y ; the particle's x-coordinate, so the x- and ; y-coordinates are now swapped LDA SZ,Y ; Fetch the Y-th particle's distance from SZ+Y into ZZ STA 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: SendSpaceViewToPPU [Show more] Type: Subroutine Category: PPU Summary: Set a new space view, clear the screen, copy the nametable buffers and configure the PPU for the new view Deep dive: Views and view types in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * LOOK1 calls SendSpaceViewToPPU

Arguments: X The space view to set: * 0 = front * 1 = rear * 2 = left * 3 = right * 4 = generating a new space view
.SendSpaceViewToPPU LDA #72 ; Set the screen height variables for a screen height of JSR SetScreenHeight ; 144 (i.e. 2 * 72) STX VIEW ; Set the current space view to X LDA #$00 ; Clear the screen and set the view type in QQ11 to $00 JSR TT66 ; (Space view with no fonts loaded) JSR CopyNameBuffer0To1 ; Copy the contents of nametable buffer 0 to nametable ; buffer JSR SendViewToPPU_b3 ; Configure the PPU for the view type in QQ11 JMP ResetStardust ; Hide the sprites for the stardust and return from the ; subroutine using a tail call
Name: SetSpaceViewInNMI [Show more] Type: Subroutine Category: Drawing the screen Summary: Change the current space view and configure the NMI to send both bitplanes to the PPU during VBlank
Context: See this subroutine on its own page References: This subroutine is called as follows: * LOOK1 calls SetSpaceViewInNMI

Arguments: X The space view to set: * 0 = front * 1 = rear * 2 = left * 3 = right * 4 = generating a new space view
.SetSpaceViewInNMI STX VIEW ; Set the current space view to X LDA #$00 ; Clear the screen and set the view type in QQ11 to $00 JSR TT66 ; (Space view with no fonts loaded) JSR CopyNameBuffer0To1 ; Copy the contents of nametable buffer 0 to nametable ; buffer LDA #80 ; Tell the PPU to send nametable entries up to tile STA lastNameTile ; 80 * 8 = 640 (i.e. to the end of tile row 19) in both STA lastNameTile+1 ; bitplanes JSR SetupViewInNMI_b3 ; Setup the view and configure the NMI to send both ; bitplanes to the PPU during VBlank ; Fall through into ResetStardust to hide the sprites ; for the stardust
Name: ResetStardust [Show more] Type: Subroutine Category: Stardust Summary: Hide the sprites for the stardust
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendSpaceViewToPPU calls ResetStardust
.ResetStardust LDX #NOST ; Set X to the maximum number of stardust particles, so ; we loop through all the particles of stardust in the ; following, hiding them all LDY #152 ; Set Y to the starting index in the sprite buffer, so ; we start hiding from sprite 152 / 4 = 38 (as each ; sprite in the buffer consists of four bytes) .rest1 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA #240 ; Set A to the y-coordinate that's just below the bottom ; of the screen, so we can hide the required sprites by ; moving them off-screen STA ySprite0,Y ; Set the y-coordinate for sprite Y / 4 to 240 to hide ; it (the division by four is because each sprite in the ; sprite buffer has four bytes of data) LDA #210 ; Set the sprite to use pattern number 210 for the STA pattSprite0,Y ; largest particle of stardust (the stardust particle ; patterns run from pattern 210 to 214, decreasing in ; size as the number increases) TXA ; Take the particle number, which is between 1 and 20 LSR A ; (as NOST is 20), and rotate it around from %76543210 ROR A ; to %10xxxxx3 (where x indicates a zero), storing the ROR A ; result as the sprite attribute AND #%11100001 ; STA attrSprite0,Y ; This sets the flip horizontally and flip vertically ; attributes to bits 0 and 1 of the particle number, and ; the palette to bit 3 of the particle number, so the ; reset stardust particles have a variety of reflections ; and palettes INY ; Add 4 to Y so it points to the next sprite's data in INY ; the sprite buffer INY INY DEX ; Decrement the loop counter in X BNE rest1 ; Loop back until we have hidden X sprites JSR WaitForNMI ; Wait until the next NMI interrupt has passed (i.e. the ; next VBlank) JSR SIGHT_b3 ; Draw the laser crosshairs ; Fall through into SetupSpaceView to finish setting up ; the space view's NMI configuration
Name: SetupSpaceView [Show more] Type: Subroutine Category: Drawing the screen Summary: Set up the NMI variables for the space view
Context: See this subroutine on its own page References: This subroutine is called as follows: * PlayDemo calls SetupSpaceView
.SetupSpaceView LDA #$FF ; Set showIconBarPointer = $FF to indicate that we STA showIconBarPointer ; should show the icon bar pointer LDA #$2C ; Set the visible colour to cyan ($2C) STA visibleColour LDA firstFreePattern ; Tell the NMI handler to send pattern entries from the STA firstPattern ; first free pattern onwards, so we don't waste time ; resending the static patterns we have already sent LDA #80 ; Tell the NMI handler to only clear nametable entries STA maxNameTileToClear ; up to tile 80 * 8 = 640 (i.e. up to the end of tile ; row 19) LDX #8 ; Tell the NMI handler to send nametable entries from STX firstNameTile ; tile 8 * 8 = 64 onwards (i.e. from the start of tile ; row 2) LDA #116 ; Tell the NMI handler to send nametable entries up to STA lastNameTile ; tile 116 * 8 = 800 (i.e. up to the end of tile row 28) ; in bitplane 0 RTS ; Return from the subroutine
Name: ECMOF [Show more] Type: Subroutine Category: Sound Summary: Switch off the E.C.M.
Context: See this subroutine on its own page References: This subroutine is called as follows: * FlightLoop4To16 calls ECMOF * RES2 calls ECMOF

Switch the E.C.M. off, turn off the dashboard bulb and make the sound of the E.C.M. switching off).
.ECMOF LDA #0 ; Set ECMA and ECMP to 0 to indicate that no E.C.M. is STA ECMA ; currently running STA ECMP LDY #2 ; Flush the sound channels for sound Y = 2 to stop the JMP FlushSpecificSound ; sound of the E.C.M. and return from the subroutine ; using a tail call
Name: SFRMIS [Show more] Type: Subroutine Category: Tactics Summary: Add an enemy missile to our local bubble of universe
Context: See this subroutine on its own page References: This subroutine is called as follows: * TACTICS (Part 5 of 7) calls SFRMIS

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 the missile to our universe with an AI flag ; of %11111110 (AI enabled, hostile, no E.C.M.) BCC sfrm1 ; The C flag will be set if the call to SFS1-2 was a ; success, so if it's clear, jump to sfrm1 to return ; from the subroutine LDA #120 ; Print recursive token 120 ("INCOMING MISSILE") as an JSR MESS ; in-flight message LDY #9 ; Call the NOISE routine with Y = 9 to make the sound JMP NOISE ; of the missile being launched and return from the ; subroutine using a tail call .sfrm1 RTS ; Return from the subroutine
Name: EXNO2 [Show more] Type: Subroutine Category: Status Summary: Process us making a kill Deep dive: Combat rank
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 5 of 16) calls EXNO2 * Main flight loop (Part 11 of 16) calls EXNO2 * TACTICS (Part 1 of 7) calls EXNO2

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.
.EXNO2 JSR IncreaseTally ; Add double the fractional kill count to the fractional ; and low bytes of our tally, setting the C flag if the ; addition overflowed BCC davidscockup ; If there is no carry, jump straight to EXNO3 to skip ; the following three instructions 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 #0 ; We now set X to a number between 0 and 4 depending on ; the z-axis distance to the exploding ship, with 0 ; for distant ships and 4 for close ships CMP #16 ; If z_hi >= 16, jump to exno1 with X = 0 BCS exno1 INX ; Increment X to 1 CMP #8 ; If z_hi >= 8, jump to exno1 with X = 1 BCS exno1 INX ; Increment X to 2 CMP #6 ; If z_hi >= 6, jump to exno1 with X = 2 BCS exno1 INX ; Increment X to 3 CMP #3 ; If z_hi >= 3, jump to exno1 with X = 3 BCS exno1 INX ; Increment X to 4 .exno1 LDY explosionSounds,X ; Set Y to the X-th sound effect from the table of ; explosion sound effect numbers JMP NOISE ; Call the NOISE routine to make the sound in Y, which ; will be the sound of a ship exploding at the specified ; distance, returning from the subroutine using a tail ; call
Name: explosionSounds [Show more] Type: Variable Category: Sound Summary: Sound numbers for explosions at different distances from our ship Deep dive: Sound effects in NES Elite
Context: See this variable on its own page References: This variable is used as follows: * EXNO2 uses explosionSounds
.explosionSounds EQUB 27 ; Ship explosion at a distance of z_hi >= 16 EQUB 23 ; Ship explosion at a distance of z_hi >=8 EQUB 14 ; Ship explosion at a distance of z_hi >= 6 EQUB 13 ; Ship explosion at a distance of z_hi >= 3 EQUB 13 ; Nearby ship explosion
Name: EXNO [Show more] Type: Subroutine Category: Sound Summary: Make the sound of a laser strike or ship explosion
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 11 of 16) calls EXNO

Make the two-part explosion sound of us making a laser strike, or of another ship exploding.
.EXNO LDY #10 ; Call the NOISE routine with Y = 10 to make the sound JMP NOISE ; of us making a hit or kill and return from the ; subroutine using a tail call
Name: TT66 [Show more] Type: Subroutine Category: Drawing the screen Summary: Clear the screen and set the new view type Deep dive: Views and view types in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIEF calls TT66 * ChangeToView calls TT66 * DEATH calls TT66 * PlayDemo calls TT66 * SendSpaceViewToPPU calls TT66 * SetNewViewType calls TT66 * SetSpaceViewInNMI calls TT66 * TBRIEF calls TT66 * TT22 calls TT66 * TT23 calls TT66 * TT66_b0 calls TT66

This routine does a similar job to the routine of the same name in the BBC Master version of Elite, but the code is significantly different.
Arguments: A The type of the new view (see QQ11 for a list of view types)
.TT66 STA QQ11 ; Set the new view type in QQ11 to A LDA QQ11a ; If bit 7 is set in either QQ11 or QQ11a, then either ORA QQ11 ; there is no dashboard in either view, or it is being BMI scrn1 ; added or removed, so jump to scrn1 to skip clearing ; the existing scanner, as we don't need to worry about ; preserving it LDA QQ11 ; If bit 7 of QQ11 is clear, then bit 7 must be clear in BPL scrn1 ; both QQ11 and QQ11a as we didn't take the branch ; above, so we are switching between views that both ; have dashboards, so jump to scrn1 to skip clearing the ; scanner as we want to retain it ; Strangely, we can never get here, as we take the first ; branch above when bit 7 of QQ11 is set, and we take ; the second branch when bit 7 of QQ11 is clear JSR ClearScanner ; Remove all ships from the scanner and hide the scanner ; sprites .scrn1 JSR WaitForPPUToFinish ; Wait until both bitplanes of the screen have been ; sent to the PPU, so the screen is fully updated and ; there is no more data waiting to be sent to the PPU JSR ClearScreen_b3 ; Clear the screen by zeroing patterns 66 to 255 in ; both pattern buffer, and clearing both nametable ; buffers to the background tile LDA #16 ; Set the text row for in-flight messages in the space STA messYC ; view to row 16 LDX #0 ; Set sendDashboardToPPU = 0 to reset the logic behind STX sendDashboardToPPU ; the sending of drawing bitplane 0 to the PPU in part 3 ; of the main loop JSR SetDrawingBitplane ; Set the drawing bitplane to bitplane 0 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 STA DTW1 ; Set bit 7 of DTW1 to indicate that we do not change ; case to lower case LDA #%00000000 ; Set DTW6 = %00000000 to disable lower case STA DTW6 STA LAS2 ; Set LAS2 = 0 to stop any laser pulsing 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 on row 1 STA XC STA YC JSR SetLinePatterns_b3 ; Load the line patterns for the new view into the ; pattern buffers ; We now set X to the type of icon bar to show in the ; new view LDA QQ11 ; If bit 6 of the new view in QQ11 is set then it LDX #$FF ; doesn't have an icon bar, so jump to scrn2 with AND #%01000000 ; X = $FF to hide the icon bar on row 27 BNE scrn2 LDX #4 ; If the new view in QQ11 is 1, then we are on the title LDA QQ11 ; screen, so jump to scrn2 with X = 4 so we show the CMP #1 ; copyright message in the icon bar BEQ scrn2 LDX #2 ; If the new view in QQ11 is %0000110x (i.e. 12 or 13, LDA QQ11 ; which are the Short-range Chart and Long-range Chart), AND #%00001110 ; jump to scrn2 with X = 2 to show the icon bar for the CMP #%00001100 ; charts BEQ scrn2 LDX #1 ; If we are not docked (QQ12 = 0), jump to scrn2 with LDA QQ12 ; X = 1 to show the Flight icon bar BEQ scrn2 LDX #0 ; Otherwise fall through into scrn2 with X = 0 to show ; the Docked icon bar .scrn2 LDA QQ11 ; If bit 7 of the new view in QQ11 is set then there is BMI scrn5 ; no dashboard, so jump to scrn5 to show the icon bar ; type with type X on row 27 ; If we get here then the new view has the dashboard, so ; we initialise it if required TXA ; Show the icon bar with type X JSR SetupIconBar_b3 LDA QQ11a ; If bit 7 of the old view in QQ11a is clear, then the BPL scrn3 ; old view has a dashboard, so jump to scrn3 to skip the ; following two instructions, as we don't need to ; initialise the dashboard JSR SetScreenForUpdate ; Get the screen ready for updating by hiding all ; sprites, after fading the screen to black if we are ; changing view JSR ResetScanner_b3 ; Reset the sprites used for drawing ships on the ; scanner .scrn3 JSR DrawDashNames_b3 ; Draw the dashboard into the nametable buffers for ; both bitplanes JSR msblob ; Display the dashboard's missile indicators in black JMP scrn8 ; Jump to scrn8 to continue setting up the view .scrn4 JMP SetViewAttrs_b3 ; Set up attribute buffer 0 for the chosen view, ; returning from the subroutine using a tail call .scrn5 ; If we get here then there is no dashboard in the new ; view TXA ; Show the icon bar with type X JSR SetupIconBar_b3 ; The next two comparisons aren't necessary as both $C4 ; and $8D have bit 4 clear, so they would be caught by ; the AND #%00010000 below anyway, but there's no harm ; in being explicit, I guess LDA QQ11 ; If the view type in QQ11 is $C4 (Game Over screen), CMP #$C4 ; jump to scrn4 to set up attribute buffer 0 and BEQ scrn4 ; return from the subroutine LDA QQ11 ; If the view type in QQ11 is $8D (Long-range Chart), CMP #$8D ; jump to scrn6 to skip loading the font into pattern BEQ scrn6 ; buffer 0 CMP #$CF ; If the view type in QQ11 is $CF (Start screen with BEQ scrn6 ; no fonts loaded), jump to scrn6 to skip loading ; the font into pattern buffer 0 AND #%00010000 ; If bit 4 of the new view in QQ11 is clear, jump to BEQ scrn6 ; scrn6 to skip loading the font into pattern buffer 0 ; If we get here then the new view we are setting up is ; not the Game Over screen, the Long-range Chart or the ; Start screen, and bit 4 of QQ11 is set LDA #66 ; Load the font into pattern buffer 0, and a set of JSR LoadNormalFont_b3 ; filled blocks into pattern buffer 1, from pattern 66 ; to 160 ; ; If the view type in QQ11 is $BB (Save and load with ; the normal and highlight fonts loaded), then this also ; loads an inverted font into pattern buffer 1, from ; pattern 66 to 160 .scrn6 LDA QQ11 ; If bit 5 of the new view in QQ11 is clear, jump to AND #%00100000 ; scrn7 to skip loading the normal font BEQ scrn7 JSR LoadHighFont_b3 ; Load the font into pattern buffer 1, and a set of ; filled blocks into pattern buffer 0, from pattern 161 ; to 255 .scrn7 ; The new view doesn't have a dashboard, so now we draw ; the left and right edges of the box on the rows where ; the dashboard would be, overwriting the edges of the ; dashboard from the old view (if it had one) LDA #1 ; Draw the left edge of the box on rows 20 to 26 STA nameBuffer0+20*32+1 STA nameBuffer0+21*32+1 STA nameBuffer0+22*32+1 STA nameBuffer0+23*32+1 STA nameBuffer0+24*32+1 STA nameBuffer0+25*32+1 STA nameBuffer0+26*32+1 LDA #2 ; Draw the right edge of the box on rows 20 to 26 STA nameBuffer0+20*32 STA nameBuffer0+21*32 STA nameBuffer0+22*32 STA nameBuffer0+23*32 STA nameBuffer0+24*32 STA nameBuffer0+25*32 STA nameBuffer0+26*32 LDA QQ11 ; If bit 6 of the new view in QQ11 is set, then there is AND #%01000000 ; no icon bar, so jump to scrn8... which has no effect, BNE scrn8 ; as that's the next instruction anyway, so presumably ; this was left behind after deleting the code that ; would be skipped .scrn8 JSR SetViewAttrs_b3 ; Set up attribute buffer 0 for the chosen view ; The six instructions between here and scrn9 have no ; effect, as we always end up at scrn9 and don't take ; any notice of the flags LDA demoInProgress ; If bit 7 of demoInProgress is set then we are BMI scrn9 ; initialising the demo, so jump to scrn9 LDA QQ11 ; If bit 7 of the new view in QQ11 is clear, jump to BPL scrn9 ; scrn9 CMP QQ11a ; If the view we are switching from in QQ11a is 0 (the BEQ scrn9 ; space view), jump to scrn9... which has no effect, ; as that's the next instruction anyway, so presumably ; this was left behind after deleting the code that ; would be skipped .scrn9 JSR DrawBoxTop ; Draw the top edge of the box along the top of the ; screen in nametable buffer 0 LDX languageIndex ; Set X to the index of the chosen language LDA QQ11 ; If this is the space view (QQ11 = 0), jump to scrn10 BEQ scrn10 ; to print the view name at the top of the screen CMP #1 ; If this is not the title screen (QQ11 = 1), jump to BNE scrn12 ; scrn12 to skip printing a title at the top of the ; screen ; If we get here then the new view is the title screen LDA #0 ; Move the text cursor to row 0 STA YC LDX languageIndex ; Move the text cursor to the correct column for the LDA xTitleScreen,X ; title screen in the chosen language STA XC LDA #30 ; Set A = 30 so we print recursive token 144 when we ; jump to scrn11 ("--- E L I T E ---") BNE scrn11 ; Jump to scrn11 to print ("--- E L I T E ---") at the ; top of the screen (this BNE is effectively a JMP as ; A is never zero) .scrn10 ; If we get here then the new view is the space view ; and we jumped here with A = 0 STA YC ; Move the text cursor to row 0 LDA xSpaceView,X ; Move the text cursor to the correct column for the STA XC ; space view name in the chosen language LDA languageNumber ; If bit 1 of languageNumber is set, then the chosen AND #%00000010 ; language is German, so jump to scrn13 to print the BNE scrn13 ; view name after the view noun (so we print "ANSICHT ; VORN" and "ANSICHT HINTEN" instead of "FRONT VIEW" ; and "REAR VIEW", for example) JSR PrintSpaceViewName ; Print the name of the current space view (i.e. ; "FRONT", "REAR", "LEFT" or "RIGHT") JSR TT162 ; Print a space LDA #175 ; Set A = 175 so the next instruction prints recursive ; token 15 ("VIEW ") .scrn11 JSR TT27_b2 ; Print the text token in A .scrn12 LDX #1 ; Move the text cursor to column 1 on row 1 STX XC STX YC DEX ; Set QQ17 = 0 to switch to ALL CAPS STX QQ17 RTS ; Return from the subroutine .scrn13 ; If we get here then we want to print the view name ; after the view noun LDA #175 ; Print recursive token 15 ("VIEW ") followed by a space JSR spc JSR PrintSpaceViewName ; Print the name of the current space view (i.e. ; "FRONT", "REAR", "LEFT" or "RIGHT") JMP scrn12 ; Jump back to scrn12 to finish off
Name: PrintSpaceViewName [Show more] Type: Subroutine Category: Text Summary: Print the name of the current space view
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT66 calls PrintSpaceViewName
.PrintSpaceViewName 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) JMP TT27_b2 ; Print recursive token 96 to 99, which will be in the ; range "FRONT" to "RIGHT", returning from the ; subroutine using a tail call
Name: Vectors_b0 [Show more] Type: Variable Category: Utility routines Summary: Vectors and padding at the end of ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this variable on its own page References: No direct references to this variable in this source file
FOR I%, P%, $BFF9 EQUB $FF ; Pad out the rest of the ROM bank with $FF NEXT IF _NTSC EQUW Interrupts_b0+$4000 ; Vector to the NMI handler in case this bank is ; loaded into $C000 during start-up (the handler ; contains an RTI so the interrupt is processed but ; has no effect) EQUW ResetMMC1_b0+$4000 ; Vector to the RESET handler in case this bank is ; loaded into $C000 during start-up (the handler ; resets the MMC1 mapper to map bank 7 into $C000 ; instead) EQUW Interrupts_b0+$4000 ; Vector to the IRQ/BRK handler in case this bank is ; loaded into $C000 during start-up (the handler ; contains an RTI so the interrupt is processed but ; has no effect) ELIF _PAL EQUW NMI ; Vector to the NMI handler EQUW ResetMMC1_b0+$4000 ; Vector to the RESET handler in case this bank is ; loaded into $C000 during start-up (the handler ; resets the MMC1 mapper to map bank 7 into $C000 ; instead) EQUW IRQ ; Vector to the IRQ/BRK handler ENDIF
Save bank0.bin
PRINT "S.bank0.bin ", ~CODE%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD% SAVE "3-assembled-output/bank0.bin", CODE%, P%, LOAD%