Skip to navigation

Elite on the BBC Micro and NES

Bank 7 (Part 4 of 4)

[NES version]

Name: SetupFullViewInNMI [Show more] Type: Subroutine Category: Drawing the screen Summary: Configure the PPU to send tiles for the full screen during VBlank
Context: See this subroutine on its own page References: This subroutine is called as follows: * PlayDemo calls SetupFullViewInNMI * UpdateScreen calls SetupFullViewInNMI
.SetupFullViewInNMI LDA #116 ; Tell the PPU to send nametable entries up to tile STA lastNameTile ; 116 * 8 = 928 (i.e. to the end of tile row 28) in both STA lastNameTile+1 ; bitplanes ; Fall through into SetupViewInNMI_b3 to setup the view ; and configure the NMI to send both bitplanes to the ; PPU during VBlank
Name: SetupViewInNMI_b3 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the SetupViewInNMI routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * SetSpaceViewInNMI calls SetupViewInNMI_b3
.SetupViewInNMI_b3 LDA #%11000000 ; Set A to the bitplane flags to set for the drawing ; bitplane in the call to SetupViewInNMI below: ; ; * Bit 2 clear = send tiles up to configured numbers ; * Bit 3 clear = don't clear buffers after sending ; * Bit 4 clear = we've not started sending data yet ; * Bit 5 clear = we have not yet sent all the data ; * Bit 6 set = send both pattern and nametable data ; * Bit 7 set = send data to the PPU ; ; Bits 0 and 1 are ignored and are always clear STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank18 BEQ bank18 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR SetupViewInNMI ; Call SetupViewInNMI, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank18 LDA storeA ; Restore the value of A that we stored above JMP SetupViewInNMI ; Call SetupViewInNMI, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: SendBitplaneToPPU_b3 [Show more] Type: Subroutine Category: PPU Summary: Call the SendBitplaneToPPU routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.SendBitplaneToPPU_b3 LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank19 BEQ bank19 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR SendBitplaneToPPU ; Call SendBitplaneToPPU, now that it is paged into ; memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank19 JMP SendBitplaneToPPU ; Call SendBitplaneToPPU, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: UpdateIconBar_b3 [Show more] Type: Subroutine Category: Icon bar Summary: Call the UpdateIconBar routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
.UpdateIconBar_b3 LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank20 BEQ bank20 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR UpdateIconBar ; Call UpdateIconBar, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank20 JMP UpdateIconBar ; Call UpdateIconBar, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: DrawScreenInNMI_b0 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the DrawScreenInNMI routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * PAUSE calls DrawScreenInNMI_b0 * PAUSE2 calls DrawScreenInNMI_b0 * UpdateSaveScreen calls DrawScreenInNMI_b0
.DrawScreenInNMI_b0 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JSR DrawScreenInNMI ; Call DrawScreenInNMI, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: SVE_b6 [Show more] Type: Subroutine Category: Save and load Summary: Call the SVE routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT102 calls SVE_b6
.SVE_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR SVE ; Call SVE, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: MVS5_b0 [Show more] Type: Subroutine Category: Moving Summary: Call the MVS5 routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * HAS1 calls MVS5_b0
.MVS5_b0 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 0 is already paged into memory, jump to CMP #0 ; bank21 BEQ bank21 PHA ; Otherwise store the current bank number on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR MVS5 ; Call MVS5, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank21 LDA storeA ; Restore the value of A that we stored above JMP MVS5 ; Call MVS5, which is already paged into memory, and ; return from the subroutine using a tail call
Name: HALL_b1 [Show more] Type: Subroutine Category: Ship hangar Summary: Call the HALL routine in ROM bank 1 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOENTRY calls HALL_b1
.HALL_b1 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR HALL ; Call HALL, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: CHPR_b2 [Show more] Type: Subroutine Category: Text Summary: Call the CHPR routine in ROM bank 2 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChangeLetter calls CHPR_b2 * InputName calls CHPR_b2 * PrintFlightMessage calls CHPR_b2
.CHPR_b2 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 2 is already paged into memory, jump to CMP #2 ; bank22 BEQ bank22 PHA ; Otherwise store the current bank number on the stack LDA #2 ; Page ROM bank 2 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR CHPR ; Call CHPR, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank22 LDA storeA ; Restore the value of A that we stored above JMP CHPR ; Call CHPR, which is already paged into memory, and ; return from the subroutine using a tail call
Name: DASC_b2 [Show more] Type: Subroutine Category: Text Summary: Call the DASC routine in ROM bank 2 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * BPRNT calls DASC_b2 * cmn calls DASC_b2 * PrintCharacter calls DASC_b2 * PrintCommanderName calls DASC_b2 * SetSelectedSystem calls DASC_b2 * TT160 calls DASC_b2 * TT161 calls DASC_b2 * TT16a calls DASC_b2 * TT210 calls DASC_b2 * TT25 calls DASC_b2
.DASC_b2 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 2 is already paged into memory, jump to CMP #2 ; bank23 BEQ bank23 PHA ; Otherwise store the current bank number on the stack LDA #2 ; Page ROM bank 2 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR DASC ; Call DASC, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank23 LDA storeA ; Restore the value of A that we stored above JMP DASC ; Call DASC, which is already paged into memory, and ; return from the subroutine using a tail call
Name: TT27_b2 [Show more] Type: Subroutine Category: Text Summary: Call the TT27 routine in ROM bank 2 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * cpl calls TT27_b2 * csh calls TT27_b2 * EQSHP calls TT27_b2 * mes9 calls TT27_b2 * MESS calls TT27_b2 * NLIN3 calls TT27_b2 * plf calls TT27_b2 * PrintEquipment calls TT27_b2 * PrintLaserView calls TT27_b2 * PrintSaveHeader calls TT27_b2 * PrintSpaceAndToken calls TT27_b2 * PrintSpacedHyphen calls TT27_b2 * PrintSpaceViewName calls TT27_b2 * PrintTokenAndColon calls TT27_b2 * PrintTokenCrTab calls TT27_b2 * prq calls TT27_b2 * spc calls TT27_b2 * STATUS calls TT27_b2 * TT147 calls TT27_b2 * TT151 calls TT27_b2 * TT162 calls TT27_b2 * TT210 calls TT27_b2 * TT213 calls TT27_b2 * TT25 calls TT27_b2 * TT60 calls TT27_b2 * TT66 calls TT27_b2 * TT67 calls TT27_b2 * TT68 calls TT27_b2 * TT70 calls TT27_b2 * TT73 calls TT27_b2
.TT27_b2 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 2 is already paged into memory, jump to CMP #2 ; bank24 BEQ bank24 PHA ; Otherwise store the current bank number on the stack LDA #2 ; Page ROM bank 2 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR TT27 ; Call TT27, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank24 LDA storeA ; Restore the value of A that we stored above JMP TT27 ; Call TT27, which is already paged into memory, and ; return from the subroutine using a tail call
Name: ex_b2 [Show more] Type: Subroutine Category: Text Summary: Call the ex routine in ROM bank 2 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * PrintMessage calls ex_b2
.ex_b2 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 2 is already paged into memory, jump to CMP #2 ; bank25 BEQ bank25 PHA ; Otherwise store the current bank number on the stack LDA #2 ; Page ROM bank 2 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR ex ; Call ex, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank25 LDA storeA ; Restore the value of A that we stored above JMP ex ; Call ex, which is already paged into memory, and ; return from the subroutine using a tail call
Name: PrintCtrlCode_b0 [Show more] Type: Subroutine Category: Text Summary: Call the PrintCtrlCode routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT27 calls PrintCtrlCode_b0
.PrintCtrlCode_b0 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JSR PrintCtrlCode ; Call PrintCtrlCode, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: SetupAfterLoad_b0 [Show more] Type: Subroutine Category: Start and end Summary: Call the SetupAfterLoad routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * SaveLoadCommander calls SetupAfterLoad_b0
.SetupAfterLoad_b0 LDA currentBank ; If ROM bank 0 is already paged into memory, jump to CMP #0 ; bank26 BEQ bank26 PHA ; Otherwise store the current bank number on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JSR SetupAfterLoad ; Call SetupAfterLoad, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank26 JMP SetupAfterLoad ; Call SetupAfterLoad, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: HideShip_b1 [Show more] Type: Subroutine Category: Dashboard Summary: Update the current ship so it is no longer shown on the scanner Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 11 of 16) calls HideShip_b1
.HideShip_b1 LDA #0 ; Zero byte #33 in the current ship's data block at K%, LDY #33 ; so it is not shown on the scanner (a non-zero byte #33 STA (INF),Y ; represents the ship's number on the scanner, with a ; ship number of zero indicating that the ship is not ; shown on the scanner) ; Fall through into HideFromScanner to hide the scanner ; sprites for this ship and reset byte #33 in the INWK ; workspace
Name: HideFromScanner_b1 [Show more] Type: Subroutine Category: Dashboard Summary: Call the HideFromScanner routine in ROM bank 1 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIEF calls HideFromScanner_b1 * KILLSHP calls HideFromScanner_b1 * TITLE calls HideFromScanner_b1
.HideFromScanner_b1 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR HideFromScanner ; Call HideFromScanner, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: TT66_b0 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the TT66 routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChooseLanguage calls TT66_b0 * HALL calls TT66_b0 * MT9 calls TT66_b0 * PAUSE calls TT66_b0 * SVE calls TT66_b0
.TT66_b0 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR TT66 ; Call TT66, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: CLIP_b1 [Show more] Type: Subroutine Category: Drawing lines Summary: Call the CLIP routine in ROM bank 1, drawing the clipped line if it fits on-screen Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawScrollFrame calls CLIP_b1
.CLIP_b1 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR CLIP ; Call CLIP, now that it is paged into memory BCS P%+5 ; If the C flag is set then the clipped line does not ; fit on-screen, so skip the next instruction JSR LOIN ; The clipped line fits on-screen, so draw it JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: ClearScreen_b3 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the ClearScreen routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * BAY calls ClearScreen_b3 * TBRIEF calls ClearScreen_b3 * TT66 calls ClearScreen_b3
.ClearScreen_b3 LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank27 BEQ bank27 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR ClearScreen ; Call ClearScreen, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank27 JMP ClearScreen ; Call ClearScreen, which is already paged into memory, ; and return from the subroutine using a tail call
Name: SCAN_b1 [Show more] Type: Subroutine Category: Dashboard Summary: Call the SCAN routine in ROM bank 1 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * MVEIT (Part 9 of 9) calls SCAN_b1
.SCAN_b1 LDA currentBank ; If ROM bank 1 is already paged into memory, jump to CMP #1 ; bank28 BEQ bank28 PHA ; Otherwise store the current bank number on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR SCAN ; Call SCAN, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank28 JMP SCAN ; Call SCAN, which is already paged into memory, and ; return from the subroutine using a tail call
Name: UpdateViewWithFade [Show more] Type: Subroutine Category: Drawing the screen Summary: Fade the screen to black, if required, hide all sprites and update the view
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIS calls UpdateViewWithFade * BRIS_b0 calls UpdateViewWithFade * TT210 calls UpdateViewWithFade
.UpdateViewWithFade JSR SetScreenForUpdate ; Get the screen ready for updating by hiding all ; sprites, after fading the screen to black if we are ; changing view ; Fall through into UpdateView to update the view
Name: UpdateView_b0 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the UpdateView routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChooseLanguage calls UpdateView_b0 * SVE calls UpdateView_b0
.UpdateView_b0 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JSR UpdateView ; Call UpdateView, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: UpdateHangarView [Show more] Type: Subroutine Category: PPU Summary: Update the hangar view on-screen by sending the data to the PPU, either immediately or during VBlank Deep dive: Views and view types in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * HALL calls UpdateHangarView
.UpdateHangarView LDA #0 ; Page ROM bank 0 into memory at $8000 (this isn't JSR SetBank ; strictly necessarily as this routine gets jumped to ; from the end of the HALL routine in bank 1, which ; itself is only called via HALL_b1, so the latter will ; revert to bank 0 following the RTS below and none of ; the following calls are to bank 0) 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 LDX #1 ; Hide bitplane 1, so: STX hiddenBitplane ; ; * Colour %01 (1) is the visible colour (cyan) ; * Colour %10 (2) is the hidden colour (black) RTS ; Return from the subroutine
Name: CLYNS [Show more] Type: Subroutine Category: Drawing the screen Summary: Clear the bottom two text rows of the visible screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChangeCmdrName calls CLYNS * dockEd calls CLYNS * FlightLoop4To16 calls CLYNS * HME2 calls CLYNS * JMTB calls CLYNS * SetSelectedSystem calls CLYNS * TT102 calls CLYNS * TT147 calls CLYNS * YESNO calls CLYNS * PrintFlightMessage calls via CLYNS+8

Other entry points: CLYNS+8 Don't zero DLY and de
.CLYNS LDA #0 ; Set the delay in DLY to 0, to indicate that we are STA DLY ; no longer showing an in-flight message, so any new ; in-flight messages will be shown instantly STA de ; Clear de, the flag that appends " DESTROYED" to the ; end of the next text token, so that it doesn't LDA #%11111111 ; Set DTW2 = %11111111 to denote that we are not STA DTW2 ; currently printing a word LDA #%10000000 ; Set bit 7 of QQ17 to switch standard tokens to STA QQ17 ; Sentence Case LDA #22 ; Move the text cursor to row 22, near the bottom of STA YC ; the screen LDA #1 ; Move the text cursor to column 1 STA XC LDA firstPattern ; Set the next free pattern number in firstFreePattern STA firstFreePattern ; to the value of firstPattern, which contains the ; number of the first pattern for which we send pattern ; data to the PPU in the NMI handler, so it's also the ; pattern we can start drawing into when we next start ; drawing into tiles LDA QQ11 ; If bit 7 of the view type in QQ11 is clear then there BPL clyn2 ; is a dashboard, so jump to clyn2 to return from the ; subroutine LDA #HI(nameBuffer0+23*32) ; Set SC(1 0) to the address of the tile in STA SC+1 ; column 0 on tile row 23 in nametable buffer 0 LDA #LO(nameBuffer0+23*32) STA SC LDA #HI(nameBuffer1+23*32) ; Set SC(1 0) to the address of the tile in STA SC2+1 ; column 0 on tile row 23 in nametable buffer 1 LDA #LO(nameBuffer1+23*32) STA SC2 LDX #2 ; We want to clear two text rows, row 23 and row 24, so ; set a counter in X to count 2 rows .CLYL JSR SetupPPUForIconBar ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDY #2 ; We are going to clear tiles from column 2 to 30 on ; each row, so set a tile index in Y to start from ; column 2 LDA #0 ; Set A = 0 to use as the pattern number for the blank ; background tile .EE2 STA (SC),Y ; Set the Y-th tile on the row in nametable buffer 0 to ; the blank tile STA (SC2),Y ; Set the Y-th tile on the row in nametable buffer 1 to ; the blank tile INY ; Increment the tile counter to move on to the next tile CPY #31 ; Loop back to blank the next tile until we have done BNE EE2 ; all the tiles up to column 30 LDA SC ; Add 32 to SC(1 0) to point to the next row, starting ADC #31 ; with the low bytes (the ADC #31 adds 32 because the C STA SC ; flag is set from the comparison above, which resulted ; in equality) STA SC2 ; Add 32 to the low byte of SC2(1 0) as well BCC clyn1 ; If the addition didn't overflow, jump to clyn1 to skip ; the high bytes INC SC+1 ; Increment the high bytes of SC(1 0) and SC2(1 0) INC SC2+1 ; to point to the next page in memory .clyn1 DEX ; Decrement the row counter in X BNE CLYL ; Loop back to blank another row, until we have done the ; number of rows in X .clyn2 RTS ; Return from the subroutine
Name: alertColours [Show more] Type: Variable Category: Status Summary: Colours for the background of the commander image to show the status condition when we are not looking at the space view
Context: See this variable on its own page References: This variable is used as follows: * DrawSpaceViewInNMI uses alertColours
.alertColours EQUB $1C ; Colour for condition Docked (medium cyan) EQUB $1A ; Colour for condition Green (medium green) EQUB $28 ; Colour for condition Yellow (light yellow) EQUB $16 ; Colour for condition Red (medium red) EQUB $06 ; Flash colour for condition Red (dark red)
Name: GetStatusCondition [Show more] Type: Subroutine Category: Status Summary: Calculate our ship's status condition
Context: See this subroutine on its own page References: This subroutine is called as follows: * DIALS calls GetStatusCondition * DrawSpaceViewInNMI calls GetStatusCondition * STATUS calls GetStatusCondition

Returns: statusCondition Our ship's status condition: * 0 = Docked * 1 = Green * 2 = Yellow * 3 = Red X Also contains the status condition
.GetStatusCondition LDX #0 ; We start with a status condition of 0, which means ; there is nothing to worry about LDY QQ12 ; Fetch the docked status from QQ12, and if we are BNE cond2 ; docked, jump to cond2 to return 0 ("Docked") as our ; status condition INX ; We are in space, so increment X to 1 ("Green") LDY JUNK ; Set Y to the number of junk items in our local bubble ; of universe (where junk is asteroids, canisters, ; escape pods and so on) LDA FRIN+2,Y ; The ship slots at FRIN are ordered with the first two ; slots reserved for the planet and sun/space station, ; and then any ships, so if the slot at FRIN+2+Y is not ; empty (i.e. is non-zero), then that means the number ; of non-asteroids in the vicinity is at least 1 BEQ cond2 ; So if X = 0, there are no ships in the vicinity, so ; jump to cond2 to store 1 ("Green") as our status ; condition INX ; Otherwise there are non-asteroids in the vicinity, so ; increment X to 2 ("Yellow") LDY statusCondition ; If the previous condition in statusCondition was 3 CPY #3 ; ("Red"), then jump to cond3 BEQ cond3 LDA ENERGY ; If our energy levels are 128 or greater, jump to cond2 BMI cond2 ; to store 2 ("Yellow") as our status condition .cond1 ; If we get here then either our energy levels are less ; than 128, or our previous condition was "Red" and our ; energy levels are less than 160 ; ; So once our energy levels are low enough to trigger a ; "Red" status, it stays that way until our energy ; levels recover to a higher level INX ; Increment X to 3 ("Red") .cond2 STX statusCondition ; Store our new status condition in statusCondition RTS ; Return from the subroutine .cond3 LDA ENERGY ; If our energy levels are less than 160, jump to cond1 CMP #160 ; to return a "Red" status condition BCC cond1 BCS cond2 ; Jump to cond2 to return a "Yellow" status condition ; (this BCS is effectively a JMP as we just passed ; through a BCC)
Name: SetupDemoUniverse [Show more] Type: Subroutine Category: Combat demo Summary: Initialise the local bubble of universe for the demo Deep dive: The NES combat demo
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH2 calls SetupDemoUniverse * SetDemoAutoPlay calls SetupDemoUniverse
.SetupDemoUniverse LDY #12 ; Wait until 12 NMI interrupts have passed (i.e. the JSR DELAY ; next 12 VBlanks) LDA #0 ; Set A = 0 CLC ; ADC #0 ; The ADC has no effect, so presumably it was left over ; from a previous version of the code STA nmiCounter ; Reset the NMI counter to zero STA nmiTimer ; Set the NMI timer to zero STA nmiTimerLo STA nmiTimerHi STA hiddenBitplane ; Set the hidden, NMI and drawing bitplanes to 0 STA nmiBitplane STA drawingBitplane 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 ResetOptions ; Reset the game options to their default values LDA #0 ; Set K%+6 to 0 so the random number seeding at the STA K%+6 ; start of the main game loop at TT100 proceeds in a ; predictable manner STA K% ; Set K% to 0 so the random number seeding at the start ; of the main flight loop at M% proceeds in a ; predictable manner ; Fall through into FixRandomNumbers to set the random ; number seeds to a known state
Name: FixRandomNumbers [Show more] Type: Subroutine Category: Combat demo Summary: Fix the random number seeds to a known value so the random numbers generated are always the same when we run the demo
Context: See this subroutine on its own page References: This subroutine is called as follows: * PlayDemo calls FixRandomNumbers
.FixRandomNumbers LDA #$75 ; Set the random number seeds to a known state, so the STA RAND ; demo plays out in the same way every time LDA #$0A STA RAND+1 LDA #$2A STA RAND+2 LDX #$E6 STX RAND+3 RTS ; Return from the subroutine
Name: ResetOptions [Show more] Type: Subroutine Category: Start and end Summary: Reset the game options to their default values
Context: See this subroutine on its own page References: This subroutine is called as follows: * SetupDemoUniverse calls ResetOptions * ShowStartScreen calls ResetOptions
.ResetOptions LDA #0 ; Configure the controller y-axis to the default STA JSTGY ; direction (i.e. not reversed) by setting JSTGY to 0 STA disableMusic ; Configure music to be enabled by default by setting ; disableMusic to 0 LDA #$FF ; Configure damping to be enabled by default by setting STA DAMP ; DAMP to $FF STA DNOIZ ; Configure sound to be enabled by default by setting ; DNOIZ to $FF RTS ; Return from the subroutine
Name: DrawTitleScreen [Show more] Type: Subroutine Category: Start and end Summary: Draw a sequence of rotating ships on-screen while checking for button presses on the controllers
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH2 calls DrawTitleScreen
.DrawTitleScreen JSR FadeToBlack_b3 ; Fade the screen to black over the next four VBlanks LDA #0 ; Set the music to tune 0 (no music) JSR ChooseMusic_b6 JSR HideMostSprites ; Hide all sprites except for sprite 0 and the icon bar ; pointer LDA #$FF ; Set the old view type in QQ11a to $FF (Segue screen STA QQ11a ; from Title screen to Demo) LDA #1 ; Set numberOfPilots = 1 to configure the game to use STA numberOfPilots ; two pilots by default (though this will probably get ; changed in the TITLE routine, or below) LDA #50 ; Set the NMI timer, which decrements each VBlank, to 50 STA nmiTimer ; so it counts down to zero and back up to 50 again LDA #0 ; Set (nmiTimerHi nmiTimerLo) = 0 so we can time how STA nmiTimerLo ; long to show the rotating ships before switching back STA nmiTimerHi ; to the Start screen .dtit1 LDY #0 ; We are about to start running through a list of ships ; to display on the title screen, so set a ship counter ; in Y .dtit2 STY titleShip ; Store the ship counter in titleShip so we can retrieve ; it below LDA titleShipType,Y ; Set A to the ship type of the ship we want to display, ; from the Y-th entry in the titleShipType table BEQ dtit1 ; If the ship type is zero then we have already worked ; our way through the list, so jump back to dtit1 to ; start from the beginning of the list again TAX ; Store the ship type in X LDA titleShipDist,Y ; Set Y to the distance of the ship we want to display, TAY ; from the Y-th entry in the titleShipDist table LDA #6 ; Call TITLE to draw the ship type in X, starting with JSR TITLE ; it far away, and bringing it to a distance of Y (the ; argument in A is ignored) BCS dtit3 ; If a button was pressed while the ship was being shown ; on-screen, TITLE will return with the C flag set, in ; which case jump to dtit3 to stop the music and return ; from the subroutine LDY titleShip ; Restore the ship counter that we stored above INY ; Increment the ship counter in Y to point to the next ; ship in the list LDA nmiTimerHi ; If the high byte of (nmiTimerHi nmiTimerLo) is still 0 CMP #1 ; then jump back to dtit2 to show the next ship BCC dtit2 ; If we get here then the NMI timer has run down to the ; point where (nmiTimerHi nmiTimerLo) is >= 256, which ; means we have shown the title screen for at least ; 50 * 256 VBlanks, as each tick of nmiTimerLo happens ; when the nmiTimer has counted down from 50 VBlanks, ; and each tick happens once every VBlank ; ; On the PAL NES, VBlank happens 50 times a second, so ; this means the title screen has been showing for 256 ; seconds, or about 4 minutes and 16 seconds ; ; On the NTSC NES, VBlank happens 60 times a second, so ; this means the title screen has been showing for 213 ; seconds, or about 3 minutes and 33 seconds LSR numberOfPilots ; Set numberOfPilots = 0 to configure the game for one ; pilot JSR ResetMusicAfterNMI ; Wait for the next NMI before resetting the current ; tune to 0 (no tune) and stopping the music JSR FadeToBlack_b3 ; Fade the screen to black over the next four VBlanks LDA languageIndex ; Set K% to the index of the currently selected STA K% ; language, so when we show the Start screen, the ; correct language is highlighted LDA #5 ; Set K%+1 = 5 to use as the value of the third counter STA K%+1 ; when deciding how long to wait on the Start screen ; before auto-playing the demo JMP ResetToStartScreen ; Reset the stack and the game's variables and show the ; Start screen, returning from the subroutine using a ; tail call .dtit3 JSR ResetMusicAfterNMI ; Wait for the next NMI before resetting the current ; tune to 0 (no tune) and stopping the music RTS ; Return from the subroutine
Name: titleShipType [Show more] Type: Variable Category: Start and end Summary: The types of ship to show rotating on the title screen
Context: See this variable on its own page References: This variable is used as follows: * DrawTitleScreen uses titleShipType
.titleShipType EQUB 11 ; Cobra Mk III EQUB 19 ; Krait EQUB 20 ; Adder EQUB 25 ; Asp Mk II EQUB 29 ; Thargoid EQUB 21 ; Gecko EQUB 18 ; Mamba EQUB 27 ; Fer-de-lance EQUB 10 ; Transporter EQUB 1 ; Missile EQUB 17 ; Sidewinder EQUB 16 ; Viper EQUB 0
Name: titleShipDist [Show more] Type: Variable Category: Start and end Summary: The distances at which to show the rotating title screen ships
Context: See this variable on its own page References: This variable is used as follows: * DrawTitleScreen uses titleShipDist
.titleShipDist EQUB 100 ; Cobra Mk III EQUB 10 ; Krait EQUB 10 ; Adder EQUB 30 ; Asp Mk II EQUB 180 ; Thargoid EQUB 10 ; Gecko EQUB 40 ; Mamba EQUB 90 ; Fer-de-lance EQUB 10 ; Transporter EQUB 70 ; Missile EQUB 40 ; Sidewinder EQUB 10 ; Viper
Name: Ze [Show more] Type: Subroutine Category: Universe Summary: Initialise the INWK workspace to a hostile ship Deep dive: Fixing ship positions
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH calls Ze * GTHG calls Ze * Main game loop (Part 3 of 6) calls Ze * Main game loop (Part 4 of 6) calls Ze

Specifically, this routine does the following: * Reset the INWK ship workspace * Set the ship to a fair distance away in all axes, in front of us but randomly up or down, left or right * Give the ship a 4% chance of having E.C.M. * Set the ship to hostile, with AI enabled This routine also sets A, X, T1 and the C flag to random values. Note that because this routine uses the value of X returned by DORND, and X contains the value of A returned by the previous call to DORND, this routine does not necessarily set the new ship to a totally random location. See the deep dive on "Fixing ship positions" for details.
.Ze JSR ZINF ; Call ZINF to reset the INWK ship workspace JSR DORND ; Set A and X to random numbers STA T1 ; Store A in T1 AND #%10000000 ; Extract the sign of A and store in x_sign STA INWK+2 JSR DORND ; Set A and X to random numbers AND #%10000000 ; Extract the sign of A and store in y_sign STA INWK+5 LDA #25 ; Set x_hi = y_hi = z_hi = 25, a fair distance away STA INWK+1 STA INWK+4 STA INWK+7 TXA ; Set the C flag if X >= 245 (4% chance) CMP #245 ROL A ; Set bit 0 of A to the C flag (i.e. there's a 4% ; chance of this ship having E.C.M.) ORA #%11000000 ; Set bits 6 and 7 of A, so the ship is hostile (bit 6 ; and has AI (bit 7) STA INWK+32 ; Store A in the AI flag of this ship JMP DORND2 ; Jump to DORND2 to set A, X and the C flag randomly, ; returning from the subroutine using a tail call
Name: UpdateSaveCount [Show more] Type: Subroutine Category: Save and load Summary: Update the save counter for the current commander
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuyAndSellCargo calls UpdateSaveCount * EQSHP calls UpdateSaveCount * TT110 calls UpdateSaveCount

Returns: A A is preserved
.UpdateSaveCount PHA ; Store A on the stack so we can retrieve it below LDA SVC ; If bit 7 of SVC is set, then we have already BMI scnt1 ; incremented the save counter for the current ; commander, so jump to scnt1 to skip the following and ; leave SVC alone CLC ; Set A = A + 1, to increment the save counter ADC #1 CMP #100 ; If A < 100, skip the following instruction BCC scnt1 LDA #0 ; Set A = 0, so the save counter goes from zero to 100 ; and around back to zero again .scnt1 ORA #%10000000 ; Set bit 7 of A to flag the save counter as increments, ; so the next call to this routine does nothing STA SVC ; Store the updated save counter in SVC PLA ; Retrieve the value of A we stored on the stack above RTS ; Return from the subroutine
Name: NLIN3 [Show more] Type: Subroutine Category: Drawing lines Summary: Print a title and draw a screen-wide horizontal line on tile row 2 to box it in
Context: See this subroutine on its own page References: This subroutine is called as follows: * EQSHP calls NLIN3 * STATUS calls NLIN3 * TT167 calls NLIN3 * TT22 calls NLIN3 * TT23 calls NLIN3 * TT25 calls NLIN3
.NLIN3 PHA ; Move the text cursor to row 0 LDA #0 STA YC PLA JSR TT27_b2 ; Print the text token in A ; Fall through into NLIN4 to draw a horizontal line at ; pixel row 19
Name: NLIN4 [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a horizontal line on tile row 2 to box in a title
Context: See this subroutine on its own page References: This subroutine is called as follows: * JMTB calls NLIN4 * SVE calls NLIN4 * TT213 calls NLIN4
.NLIN4 LDA #4 ; Set A = 4, though this has no effect other than making ; the BNE work, as NLIN2 overwrites this value BNE NLIN2 ; Jump to NLIN2 to draw the line, (this BNE is ; effectively a JMP as A is never zero) LDA #1 ; These instructions appear to be unused STA YC LDA #4
Name: NLIN2 [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a horizontal line on tile row 2 to box in a title
Context: See this subroutine on its own page References: This subroutine is called as follows: * NLIN4 calls NLIN2 * TT22 calls NLIN2
.NLIN2 JSR SetupPPUForIconBar ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDY #1 ; We now draw a horizontal line into the nametable ; buffer starting at column 1, so set Y as a counter for ; the column number LDA #3 ; Set A to tile 3 so we draw the line as a horizontal ; line that's three pixels thick .nlin1 STA nameBuffer0+2*32,Y ; Set the Y-th tile on row 2 of nametable buffer 0 to ; to tile 3 INY ; Increment the column counter CPY #32 ; Keep drawing tile 3 along row 2 until we have drawn BNE nlin1 ; column 31 RTS ; Return from the subroutine
Name: SetDrawingPlaneTo0 [Show more] Type: Subroutine Category: Drawing the screen Summary: Set the drawing bitplane to 0
Context: See this subroutine on its own page References: This subroutine is called as follows: * ResetVariables calls SetDrawingPlaneTo0
.SetDrawingPlaneTo0 LDX #0 ; Set the drawing bitplane to 0 JSR SetDrawingBitplane RTS ; Return from the subroutine
Name: ResetBuffers [Show more] Type: Subroutine Category: Drawing the screen Summary: Reset the pattern and nametable buffers
Context: See this subroutine on its own page References: This subroutine is called as follows: * ResetVariables calls ResetBuffers

The pattern buffers are in a continuous block of memory as follows: * pattBuffer0 ($6000 to $67FF) * pattBuffer1 ($6800 to $6FFF) * nameBuffer0 ($7000 to $73BF) * attrBuffer0 ($73C0 to $73FF) * nameBuffer1 ($7400 to $77BF) * attrBuffer1 ($77C0 to $77FF) This covers $1800 bytes (24 pages of memory), and this routine zeroes the whole lot.
.ResetBuffers LDA #HI(pattBuffer0) ; Set SC2(1 0) to pattBuffer0, the address of the first STA SC2+1 ; of the buffers we want to clear LDA #Lo(pattBuffer0) STA SC2 LDY #0 ; Set Y as a byte counter that we can use as an index ; into each page of memory as we clear them LDX #$18 ; We want to zero memory from $6000 to $7800, so set a ; page counter in X to count each page of memory as we ; clear them LDA #0 ; We are going to clear the buffers by filling them with ; zeroes, so set A = 0 so we can poke it into memory .rbuf1 STA (SC2),Y ; Zero the Y-th byte of SC2(1 0) INY ; Increment the byte counter BNE rbuf1 ; Loop back until we have zeroed a full page of memory INC SC2+1 ; Increment the high byte of SC2(1 0) so it points to ; the next page in memory DEX ; Decrement the page counter in X BNE rbuf1 ; Loop back until we have zeroed all X pages of memory RTS ; Return from the subroutine
Name: DORND [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Generate random numbers Deep dive: Generating random numbers Fixing ship positions
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH calls DORND * DETOK2 calls DORND * DrawLightning calls DORND * ESCAPE calls DORND * GVL calls DORND * HALL calls DORND * HANGER calls DORND * HAS1 calls DORND * LASLI calls DORND * LL164 calls DORND * LL9 (Part 1 of 12) calls DORND * Main flight loop (Part 8 of 16) calls DORND * Main flight loop (Part 11 of 16) calls DORND * Main game loop (Part 1 of 6) calls DORND * Main game loop (Part 2 of 6) calls DORND * Main game loop (Part 4 of 6) calls DORND * Main game loop (Part 5 of 6) calls DORND * MT18 calls DORND * nWq calls DORND * OUCH calls DORND * PLFL calls DORND * SFS1 calls DORND * ShowScrollText calls DORND * SOLAR calls DORND * SPIN calls DORND * STARS1 calls DORND * STARS2 calls DORND * STARS6 calls DORND * TACTICS (Part 1 of 7) calls DORND * TACTICS (Part 2 of 7) calls DORND * TACTICS (Part 3 of 7) calls DORND * TACTICS (Part 4 of 7) calls DORND * TACTICS (Part 5 of 7) calls DORND * TACTICS (Part 6 of 7) calls DORND * TACTICS (Part 7 of 7) calls DORND * TT18 calls DORND * TT210 calls DORND * Ze calls DORND * DrawExplosionBurst calls via DORND2 * Ze calls via DORND2

Set A and X to random numbers (though note that X is set to the random number that was returned in A the last time DORND was called). The C and V flags are also set randomly. If we want to generate a repeatable sequence of random numbers, when generating explosion clouds, for example, then we call DORND2 to ensure that the value of the C flag on entry doesn't affect the outcome, as otherwise we might not get the same sequence of numbers if the C flag changes.
Other entry points: DORND2 Make sure the C flag doesn't affect the outcome
.DORND2 CLC ; Clear the C flag so the value of the C flag on entry ; doesn't affect the outcome .DORND LDA RAND ; Calculate the next two values f2 and f3 in the feeder ROL A ; sequence: TAX ; ADC RAND+2 ; * f2 = (f1 << 1) mod 256 + C flag on entry STA RAND ; * f3 = f0 + f2 + (1 if bit 7 of f1 is set) STX RAND+2 ; * C flag is set according to the f3 calculation LDA RAND+1 ; Calculate the next value m2 in the main sequence: TAX ; ADC RAND+3 ; * A = m2 = m0 + m1 + C flag from feeder calculation STA RAND+1 ; * X = m1 STX RAND+3 ; * C and V flags set according to the m2 calculation RTS ; Return from the subroutine
Name: PROJ [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Project the current ship or planet onto the screen Deep dive: Extended screen coordinates
Context: See this subroutine on its own page References: This subroutine is called as follows: * PLANET calls PROJ * SHPPT calls PROJ

Project the current ship's location or the planet onto the screen, either returning the screen coordinates of the projection (if it's on-screen), or returning an error via the C flag. In this context, "on-screen" means that the point is projected into the following range: centre of screen - 1024 < x < centre of screen + 1024 centre of screen - 1024 < y < centre of screen + 1024 This is to cater for ships (and, more likely, planets and suns) whose centres are off-screen but whose edges may still be visible. The projection calculation is: K3(1 0) = #X + x / z K4(1 0) = #Y + y / z where #X and #Y are the pixel x-coordinate and y-coordinate of the centre of the screen.
Arguments: INWK The ship data block for the ship to project on-screen
Returns: K3(1 0) The x-coordinate of the ship's projection on-screen K4(1 0) The y-coordinate of the ship's projection on-screen C flag Set if the ship's projection doesn't fit on the screen, clear if it does project onto the screen A Contains K4+1, the high byte of the y-coordinate
.PROJ LDA INWK ; Set P(1 0) = (x_hi x_lo) STA P ; = x LDA INWK+1 STA P+1 LDA INWK+2 ; Set A = x_sign JSR PLS6 ; Call PLS6 to calculate: ; ; (X K) = (A P) / (z_sign z_hi z_lo) ; = (x_sign x_hi x_lo) / (z_sign z_hi z_lo) ; = x / z BCS PL21S-1 ; If the C flag is set then the result overflowed and ; the coordinate doesn't fit on the screen, so return ; from the subroutine with the C flag set (as PL21S-1 ; contains an RTS) LDA K ; Set K3(1 0) = (X K) + #X ADC #X ; = #X + x / z STA K3 ; ; first doing the low bytes TXA ; And then the high bytes. #X is the x-coordinate of ADC #0 ; the centre of the space view, so this converts the STA K3+1 ; space x-coordinate into a screen x-coordinate LDA INWK+3 ; Set P(1 0) = (y_hi y_lo) STA P LDA INWK+4 STA P+1 LDA INWK+5 ; Set A = -y_sign EOR #%10000000 JSR PLS6 ; Call PLS6 to calculate: ; ; (X K) = (A P) / (z_sign z_hi z_lo) ; = -(y_sign y_hi y_lo) / (z_sign z_hi z_lo) ; = -y / z BCS PL21S-1 ; If the C flag is set then the result overflowed and ; the coordinate doesn't fit on the screen, so return ; from the subroutine with the C flag set (as PL21S-1 ; contains an RTS) LDA K ; Set K4(1 0) = (X K) + halfScreenHeight ADC halfScreenHeight ; = halfScreenHeight - y / z STA K4 ; ; first doing the low bytes TXA ; And then the high bytes. halfScreenHeight is the ADC #0 ; y-coordinate of the centre of the space view, so this STA K4+1 ; converts the space x-coordinate into a screen ; y-coordinate CLC ; Clear the C flag to indicate success RTS ; Return from the subroutine
Name: PLS6 [Show more] Type: Subroutine Category: Drawing planets Summary: Calculate (X K) = (A P) / (z_sign z_hi z_lo)
Context: See this subroutine on its own page References: This subroutine is called as follows: * PROJ calls PLS6 * PROJ calls via PL21S-1

Calculate the following: (X K) = (A P) / (z_sign z_hi z_lo) returning an overflow in the C flag if the result is >= 1024.
Arguments: INWK The planet or sun's ship data block
Returns: C flag Set if the result >= 1024, clear otherwise
Other entry points: PL21S-1 Set the C flag and return from the subroutine
.PL21S SEC ; Set the C flag to indicate an overflow RTS ; Return from the subroutine .PLS6 JSR DVID3B2 ; Call DVID3B2 to calculate: ; ; K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_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 K+3 ; Set A = |K+3| OR K+2 AND #%01111111 ORA K+2 BNE PL21S ; If A is non-zero then the two high bytes of K(3 2 1 0) ; are non-zero, so jump to PL21S to set the C flag and ; return from the subroutine ; We can now just consider K(1 0), as we know the top ; two bytes of K(3 2 1 0) are both 0 LDX K+1 ; Set X = K+1, so now (X K) contains the result in ; K(1 0), which is the format we want to return the ; result in CPX #4 ; If the high byte of K(1 0) >= 4 then the result is BCS PL6 ; >= 1024, so return from the subroutine with the C flag ; set to indicate an overflow (as PL6 contains an RTS) LDA K+3 ; Fetch the sign of the result from K+3 (which we know ; has zeroes in bits 0-6, so this just fetches the sign) BPL PL6 ; If the sign bit is clear and the result is positive, ; then the result is already correct, so return from ; the subroutine with the C flag clear to indicate ; success (as PL6 contains an RTS) LDA K ; Otherwise we need to negate the result, which we do EOR #%11111111 ; using two's complement, starting with the low byte: ADC #1 ; STA K ; K = ~K + 1 TXA ; And then the high byte: EOR #%11111111 ; ADC #0 ; X = ~X TAX CLC ; Clear the C flag to indicate success .PL6 RTS ; Return from the subroutine
Name: UnpackToRAM [Show more] Type: Subroutine Category: Utility routines Summary: Unpack compressed image data to RAM Deep dive: Image and data compression
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawBigLogo calls UnpackToRAM * GetHeadshot calls UnpackToRAM * GetSystemBack calls UnpackToRAM * SetViewAttrs calls UnpackToRAM

This routine unpacks compressed data into RAM. The data is typically nametable or pattern data that is unpacked into the nametable or pattern buffers. The algorithm is described in the deep dive on "Image and data compression".
.UnpackToRAM LDY #0 ; We work our way through the packed data at SC(1 0), so ; set an index counter in Y, starting from the first ; data byte at offset zero .upac1 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 ; Set X = 0, so we can use a LDA (V,X) instruction below ; to fetch the next data byte from V(1 0), as the 6502 ; doesn't have a LDA (V) instruction LDA (V,X) ; Set A to the byte of packed data at V(1 0), which is ; the next byte of data to unpack ; ; As X = 0, this instruction is effectively LDA (V), ; which isn't a valid 6502 instruction on its own INC V ; Increment V(1 0) to point to the next byte of packed BNE upac2 ; data INC V+1 .upac2 CMP #$40 ; If A >= $40, jump to unpac12 to output the data in A BCS upac12 ; as it is, and move on to the next byte ; If we get here then we know that the data byte in A is ; of the form $0x, $1x, $2x or $3x TAX ; Store the packed data byte in X so we can retrieve it ; below AND #$0F ; If the data byte in A is in the format $x0, jump to BEQ upac11 ; upac11 to output the data in X as it is, and move on ; to the next byte CPX #$3F ; If the data byte in X is $3F, then this indicates we BEQ upac13 ; have reached the end of the packed data, so jump to ; upac13 to return from the subroutine, as we are done TXA ; Set A back to the unpacked data byte, which we stored ; in X above CMP #$20 ; If A >= $20, jump to upac6 to process values of $2x BCS upac6 ; and $3x (as we already processed values above $40) ; If we get here then we know that the data byte in A is ; of the form $0x or $1x (and not $x0) CMP #$10 ; If A >= $10, set the C flag AND #$0F ; Set X to the low nibble of A, so it contains the TAX ; number of zeroes or $FF bytes that we need to output ; when the data byte is $0x or $1x BCS upac5 ; If the data byte in A was >= $10, then we know that A ; is of the form $1x, so jump to upac5 to output the ; number of $FF bytes specified in X ; If we get here then we know that A is of the form ; $0x, so we need to output the number of zero bytes ; specified in X LDA #0 ; Set A as the byte to write to SC(1 0), so we output ; zeroes .upac3 ; This loop writes byte A to SC(1 0), X times STA (SC),Y ; Write the byte in A to SC(1 0) INY ; Increment Y to point to the next data byte BNE upac4 ; If Y has now wrapped round to zero, loop back to upac1 ; to unpack the next data byte INC SC+1 ; Otherwise Y is now zero, so increment the high byte of ; SC(1 0) to point to the next page, so that SC(1 0) + Y ; still points to the next data byte .upac4 DEX ; Decrement the byte counter in X BNE upac3 ; Loop back to upac3 to write the byte in A again, until ; we have written it X times JMP upac1 ; Jump back to upac1 to unpack the next byte .upac5 ; If we get here then we know that A is of the form ; $1x, so we need to output the number of $FF bytes ; specified in X LDA #$FF ; Set A as the byte to write to SC(1 0), so we output ; $FF BNE upac3 ; Jump to the loop at upac3 to output $FF to SC(1 0), ; X times (this BNE is effectively a JMP as A is never ; zero) .upac6 ; If we get here then we know that the data byte in A is ; of the form $2x or $3x (and not $x0) LDX #0 ; Set X = 0, so we can use a LDA (V,X) instruction below ; to fetch the next data byte from V(1 0), as the 6502 ; doesn't have a LDA (V) instruction CMP #$30 ; If A >= $30 then jump to upac7 to process bytes in the BCS upac7 ; for $3x ; If we get here then we know that the data byte in A is ; of the form $2x (and not $x0) AND #$0F ; Set T to the low nibble of A, so it contains the STA T ; number of times that we need to output the byte ; following the $2x data byte LDA (V,X) ; Set A to the byte of packed data at V(1 0), which is ; the next byte of data to unpack, i.e. the byte that we ; need to write X times ; ; As X = 0, this instruction is effectively LDA (V), ; which isn't a valid 6502 instruction on its own LDX T ; Set X to the number of times we need to output the ; byte in A INC V ; Increment V(1 0) to point to the next data byte (as we BNE upac3 ; just read the one after the $2x data byte), and jump INC V+1 ; to the loop in upac3 to output the byte in A, X times JMP upac3 .upac7 ; If we get here then we know that the data byte in A is ; of the form $3x (and not $x0), and we jump here with ; X set to 0 AND #$0F ; Set T to the low nibble of A, so it contains the STA T ; number of unchanged bytes that we need to output ; following the $3x data byte .upac8 LDA (V,X) ; Set A to the byte of packed data at V(1 0), which is ; the next byte of data to unpack ; ; As X = 0, this instruction is effectively LDA (V), ; which isn't a valid 6502 instruction on its own INC V ; Increment V(1 0) to point to the next data byte (as we BNE upac9 ; just read the one after the $2x data byte) INC V+1 ; We now loop T times, outputting the next data byte on ; each iteration, so we end up writing the next T bytes ; unchanged .upac9 STA (SC),Y ; Write the unpacked data in A to the Y-th byte of ; SC(1 0) INY ; Increment Y to point to the next data byte BNE upac10 ; If Y has now wrapped round to zero, increment the INC SC+1 ; high byte of SC(1 0) to point to the next page, so ; that SC(1 0) + Y still points to the next data byte .upac10 DEC T ; Decrement the loop counter in T BNE upac8 ; Loop back until we have copied the next T bytes ; unchanged from V(1 0) to SC(1 0) JMP upac1 ; Jump back to upac1 to unpack the next byte .upac11 TXA ; Set A back to the unpacked data byte, which we stored ; in X before jumping here .upac12 STA (SC),Y ; Write the unpacked data in A to the Y-th byte of ; SC(1 0) INY ; Increment Y to point to the next data byte BNE upac1 ; If Y has now wrapped round to zero, loop back to upac1 ; to unpack the next data byte INC SC+1 ; Otherwise Y is now zero, so increment the high byte of JMP upac1 ; SC(1 0) to point to the next page, so that SC(1 0) + Y ; still points to the next data byte .upac13 RTS ; Return from the subroutine
Name: UnpackToPPU [Show more] Type: Subroutine Category: Utility routines Summary: Unpack compressed image data and send it to the PPU Deep dive: Image and data compression
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetCmdrImage calls UnpackToPPU * GetSystemImage calls UnpackToPPU * SendDashImageToPPU calls UnpackToPPU * SendViewToPPU calls UnpackToPPU * GetSystemImage calls via UnpackToPPU+2

This routine unpacks compressed data and sends it straight to the PPU. The data is typically nametable or pattern data that is unpacked into the PPU's nametable or pattern tables. The algorithm is described in the deep dive on "Image and data compression".
Other entry points: UnpackToPPU+2 Unpack data from offset Y onwards
.UnpackToPPU LDY #0 ; We work our way through the packed data at SC(1 0), so ; set an index counter in Y, starting from the first ; data byte at offset zero .upak1 LDA (V),Y ; Set A to the Y-th byte of packed data at V(1 0), which ; is the next byte of data to unpack INY ; Increment Y to point to the next byte of packed data BNE upak2 ; If Y has now wrapped round to zero, increment the INC V+1 ; high byte of V(1 0) to point to the next page, so ; that V(1 0) + Y still points to the next data byte .upak2 CMP #$40 ; If A >= $40, jump to upak10 to send the data in A BCS upak10 ; as it is, and move on to the next byte TAX ; Store the packed data byte in X so we can retrieve it ; below AND #$0F ; If the data byte in A is in the format $x0, jump to BEQ upak9 ; upak9 to send the data in X as it is, and move on ; to the next byte CPX #$3F ; If the data byte in X is $3F, then this indicates we BEQ upak11 ; have reached the end of the packed data, so jump to ; upak11 to return from the subroutine, as we are done TXA ; Set A back to the unpacked data byte, which we stored ; in X above CMP #$20 ; If A >= $20, jump to upak5 to process values of $2x BCS upak5 ; and $3x (as we already processed values above $40) ; If we get here then we know that the data byte in A is ; of the form $0x or $1x (and not $x0) CMP #$10 ; If A >= $10, set the C flag AND #$0F ; Set X to the low nibble of A, so it contains the TAX ; number of zeroes or $FF bytes that we need to send ; when the data byte is $0x or $1x BCS upak4 ; If the data byte in A was >= $10, then we know that A ; is of the form $1x, so jump to upak4 to send the ; number of $FF bytes specified in X ; If we get here then we know that A is of the form ; $0x, so we need to send the number of zero bytes ; specified in X LDA #0 ; Set A as the byte to write to the PPU, so we send ; zeroes .upak3 ; This loop sends byte A to the PPU, X times STA PPU_DATA ; Send the byte in A to the PPU DEX ; Decrement the byte counter in X BNE upak3 ; Loop back to upak3 to send the byte in A again, until ; we have sent it X times JMP upak1 ; Jump back to upak1 to unpack the next byte .upak4 ; If we get here then we know that A is of the form ; $1x, so we need to send the number of $FF bytes ; specified in X LDA #$FF ; Set A as the byte to send to the PPU BNE upak3 ; Jump to the loop at upak3 to send $FF to the PPU, ; X times (this BNE is effectively a JMP as A is never ; zero) .upak5 ; If we get here then we know that the data byte in A is ; of the form $2x or $3x (and not $x0) CMP #$30 ; If A >= $30 then jump to upak6 to process bytes in the BCS upak6 ; for $3x AND #$0F ; Set X to the low nibble of A, so it contains the TAX ; number of times that we need to send the byte ; following the $2x data byte LDA (V),Y ; Set A to the Y-th byte of packed data at V(1 0), which ; is the next byte of data to unpack, i.e. the byte that ; we need to write X times INY ; Increment Y to point to the next byte of packed data BNE upak3 ; If Y has now wrapped round to zero, increment the INC V+1 ; high byte of V(1 0) to point to the next page, so JMP upak3 ; that V(1 0) + Y still points to the next data byte, ; and jump to the loop in upak3 to send the byte in A, ; X times .upak6 ; If we get here then we know that the data byte in A is ; of the form $3x (and not $x0), and we jump here with ; X set to 0 AND #$0F ; Set X to the low nibble of A, so it contains the TAX ; number of unchanged bytes that we need to send ; following the $3x data byte .upak7 LDA (V),Y ; Set A to the Y-th byte of packed data at V(1 0), which ; is the next byte of data to unpack INY ; Increment Y to point to the next byte of packed data BNE upak8 ; If Y has now wrapped round to zero, increment the INC V+1 ; high byte of V(1 0) to point to the next page, so ; that V(1 0) + Y still points to the next data byte .upak8 STA PPU_DATA ; Send the unpacked data in A to the PPU DEX ; Decrement the byte counter in X BNE upak7 ; Loop back to upak7 to send the next byte, until we ; have sent the next X bytes JMP upak1 ; Jump back to upak1 to unpack the next byte .upak9 TXA ; Set A back to the unpacked data byte, which we stored ; in X before jumping here .upak10 STA PPU_DATA ; Send the byte in A to the PPU JMP upak1 ; Jump back to upak1 to unpack the next byte .upak11 RTS ; Return from the subroutine
Name: FAROF2 [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Compare x_hi, y_hi and z_hi with A
Context: See this subroutine on its own page References: This subroutine is called as follows: * SpawnSpaceStation calls FAROF2

Calculate the distance from the origin to the point (x, y, z) and compare it with the argument A, clearing the C flag if the distance is < A, or setting the C flag if the distance is >= A. 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 and the result is returned with the C flag the other way around. 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 component 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 check against in A is not halved, so the comparison ends up being between |(x y z)| and A * 2.
Arguments: A The distance to check against (the distance is checked against A * 2)
Returns: C flag The result of comparing |x y z| with A: * Clear if the distance to (x, y, z) < A * 2 * Set if the distance to (x, y, z) >= A * 2
.FAROF2 STA T ; Store the value that we want to compare x, y z with ; in T 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+2 ; If any of x_sign, y_sign or z_sign are non-zero ORA INWK+5 ; (ignoring the sign in bit 7), then jump to farr3 to ORA INWK+8 ; return the C flag set, to indicate that A is smaller ASL A ; than x, y, z BNE farr3 LDA INWK+7 ; Set K+2 = z_hi / 2 LSR A STA K+2 LDA INWK+1 ; Set K = x_hi / 2 LSR A STA K LDA INWK+4 ; 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 / 2 CMP K ; If A >= K, jump to farr1 to skip the next instruction BCS farr1 LDA K ; Set A = K, so A = max(K, K+1) .farr1 CMP K+2 ; If A >= K+2, jump to farr2 to skip the next BCS farr2 ; instruction LDA K+2 ; Set A = K+2, so A = max(A, K+2) ; = max(K, K+1, K+2) .farr2 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 RTS ; Return from the subroutine .farr3 SEC ; Set the C flag to indicate A < x and A < y and A < z RTS ; Return from the subroutine
Name: MU5 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Set K(3 2 1 0) = (A A A A) and clear the C flag
Context: See this subroutine on its own page References: This subroutine is called as follows: * MULT3 calls MU5

In practice this is only called via a BEQ following an AND instruction, in which case A = 0, so this routine effectively does this: K(3 2 1 0) = 0
.MU5 STA K ; Set K(3 2 1 0) to (A A A A) STA K+1 STA K+2 STA K+3 CLC ; Clear the C flag RTS ; Return from the subroutine
Name: MULT3 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate K(3 2 1 0) = (A P+1 P) * Q Deep dive: Shift-and-add multiplication
Context: See this subroutine on its own page References: This subroutine is called as follows: * MV40 calls MULT3

Calculate the following multiplication between a signed 24-bit number and a signed 8-bit number, returning the result as a signed 32-bit number: K(3 2 1 0) = (A P+1 P) * Q The algorithm is the same shift-and-add algorithm as in routine MULT1, but extended to cope with more bits.
Returns: C flag The C flag is cleared
.MULT3 STA R ; Store the high byte of (A P+1 P) in R AND #%01111111 ; Set K+2 to |A|, the high byte of K(2 1 0) STA K+2 LDA Q ; Set A to bits 0-6 of Q, so A = |Q| AND #%01111111 BEQ MU5 ; If |Q| = 0, jump to MU5 to set K(3 2 1 0) to 0, ; returning from the subroutine using a tail call SEC ; Set T = |Q| - 1 SBC #1 STA T ; We now use the same shift-and-add algorithm as MULT1 ; to calculate the following: ; ; K(2 1 0) = K(2 1 0) * |Q| ; ; so we start with the first shift right, in which we ; take (K+2 P+1 P) and shift it right, storing the ; result in K(2 1 0), ready for the multiplication loop ; (so the multiplication loop actually calculates ; (|A| P+1 P) * |Q|, as the following sets K(2 1 0) to ; (|A| P+1 P) shifted right) LDA P+1 ; Set A = P+1 LSR K+2 ; Shift the high byte in K+2 to the right ROR A ; Shift the middle byte in A to the right and store in STA K+1 ; K+1 (so K+1 contains P+1 shifted right) LDA P ; Shift the middle byte in P to the right and store in ROR A ; K, so K(2 1 0) now contains (|A| P+1 P) shifted right STA K 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 ; We now use the same shift-and-add algorithm as MULT1 ; to calculate the following: ; ; K(2 1 0) = K(2 1 0) * |Q| LDA #0 ; Set A = 0 so we can start building the answer in A LDX #24 ; Set up a counter in X to count the 24 bits in K(2 1 0) .MUL2 BCC P%+4 ; If C (i.e. the next bit from K) is set, do the ADC T ; addition for this bit of K: ; ; A = A + T + C ; = A + |Q| - 1 + 1 ; = A + |Q| ROR A ; Shift A right by one place to catch the next digit ROR K+2 ; next digit of our result in the left end of K(2 1 0), ROR K+1 ; while also shifting K(2 1 0) right to fetch the next ROR K ; bit for the calculation into the C flag ; ; On the last iteration of this loop, the bit falling ; off the end of K will be bit 0 of the original A, as ; we did one shift before the loop and we are doing 24 ; iterations. We set A to 0 before looping, so this ; means the loop exits with the C flag clear DEX ; Decrement the loop counter BNE MUL2 ; Loop back for the next bit until K(2 1 0) has been ; rotated all the way ; The result (|A| P+1 P) * |Q| is now in (A K+2 K+1 K), ; but it is positive and doesn't have the correct sign ; of the final result yet STA T ; Save the high byte of the result into T 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 R ; Fetch the sign byte from the original (A P+1 P) ; argument that we stored in R EOR Q ; EOR with Q so the sign bit is the same as that of ; (A P+1 P) * Q AND #%10000000 ; Extract the sign bit ORA T ; Apply this to the high byte of the result in T, so ; that A now has the correct sign for the result, and ; (A K+2 K+1 K) therefore contains the correctly signed ; result STA K+3 ; Store A in K+3, so K(3 2 1 0) now contains the result RTS ; Return from the subroutine
Name: MLS2 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (S R) = XX(1 0) and (A P) = A * ALP1
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS1 calls MLS2 * STARS6 calls MLS2

Calculate the following: (S R) = XX(1 0) (A P) = A * ALP1 where ALP1 is the magnitude of the current roll angle alpha, in the range 0-31.
.MLS2 LDX XX ; Set (S R) = XX(1 0), starting with the low bytes STX R LDX XX+1 ; And then doing the high bytes STX S ; Fall through into MLS1 to calculate (A P) = A * ALP1
Name: MLS1 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = ALP1 * A
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS1 calls MLS1 * STARS6 calls MLS1 * STARS1 calls via MULTS-2 * STARS2 calls via MULTS-2 * STARS6 calls via MULTS-2

Calculate the following: (A P) = ALP1 * A where ALP1 is the magnitude of the current roll angle alpha, in the range 0-31. This routine uses an unrolled version of MU11. MU11 calculates P * X, so we use the same algorithm but with P set to ALP1 and X set to A. The unrolled version here can skip the bit tests for bits 5-7 of P as we know P < 32, so only 5 shifts with bit tests are needed (for bits 0-4), while the other 3 shifts can be done without a test (for bits 5-7).
Other entry points: MULTS-2 Calculate (A P) = X * A
.MLS1 LDX ALP1 ; Set P to the roll angle alpha magnitude in ALP1 STX P ; (0-31), so now we calculate P * A .MULTS TAX ; Set X = A, so now we can calculate P * X instead of ; P * A to get our result, and we can use the algorithm ; from MU11 to do that, just unrolled (as MU11 returns ; P * X) AND #%10000000 ; Set T to the sign bit of A STA T TXA ; Set A = |A| AND #127 BEQ MU6 ; If A = 0, jump to MU6 to set P(1 0) = 0 and return ; from the subroutine using a tail call TAX ; Set T1 = X - 1 DEX ; STX T1 ; We subtract 1 as the C flag will be set when we want ; to do an addition in the loop below LDA #0 ; Set A = 0 so we can start building the answer in A LSR P ; Set P = P >> 1 ; and C flag = bit 0 of P ; We are now going to work our way through the bits of ; P, and do a shift-add for any bits that are set, ; keeping the running total in A, but instead of using a ; loop like MU11, we just unroll it, starting with bit 0 BCC P%+4 ; If C (i.e. the next bit from P) is set, do the ADC T1 ; addition for this bit of P: ; ; A = A + T1 + C ; = A + X - 1 + 1 ; = A + X ROR A ; Shift A right to catch the next digit of our result, ; which the next ROR sticks into the left end of P while ; also extracting the next bit of P ROR P ; Add the overspill from shifting A to the right onto ; the start of P, and shift P right to fetch the next ; bit for the calculation into the C flag BCC P%+4 ; Repeat the shift-and-add loop for bit 1 ADC T1 ROR A ROR P BCC P%+4 ; Repeat the shift-and-add loop for bit 2 ADC T1 ROR A ROR P BCC P%+4 ; Repeat the shift-and-add loop for bit 3 ADC T1 ROR A ROR P BCC P%+4 ; Repeat the shift-and-add loop for bit 4 ADC T1 ROR A ROR P LSR A ; Just do the "shift" part for bit 5 ROR P LSR A ; Just do the "shift" part for bit 6 ROR P LSR A ; Just do the "shift" part for bit 7 ROR P ORA T ; Give A the sign bit of the original argument A that ; we put into T above RTS ; Return from the subroutine
Name: MU6 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Set P(1 0) = (A A)
Context: See this subroutine on its own page References: This subroutine is called as follows: * MLS1 calls MU6

In practice this is only called via a BEQ following an AND instruction, in which case A = 0, so this routine effectively does this: P(1 0) = 0
.MU6 STA P+1 ; Set P(1 0) = (A A) STA P RTS ; Return from the subroutine
Name: SQUA [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Clear bit 7 of A and calculate (A P) = A * A
Context: See this subroutine on its own page References: This subroutine is called as follows: * NORM calls SQUA

Do the following multiplication of unsigned 8-bit numbers, after first clearing bit 7 of A: (A P) = A * A
.SQUA AND #%01111111 ; Clear bit 7 of A and fall through into SQUA2 to set ; (A P) = A * A
Name: SQUA2 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = A * A
Context: See this subroutine on its own page References: This subroutine is called as follows: * HITCH calls SQUA2 * MAS3 calls SQUA2 * PLFL calls SQUA2 * SUN (Part 1 of 2) calls SQUA2 * TT111 calls SQUA2

Do the following multiplication of unsigned 8-bit numbers: (A P) = A * A
.SQUA2 STA P ; Copy A into P and X TAX BNE MU11 ; If X = 0 fall through into MU1 to return a 0, ; otherwise jump to MU11 to return P * X
Name: MU1 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Copy X into P and A, and clear the C flag
Context: See this subroutine on its own page References: This subroutine is called as follows: * MULTU calls MU1

Used to return a 0 result quickly from MULTU below.
.MU1 CLC ; Clear the C flag STX P ; Copy X into P and A TXA RTS ; Return from the subroutine
Name: MLU1 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate Y1 = y_hi and (A P) = |y_hi| * Q for Y-th stardust
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS1 calls MLU1 * STARS6 calls MLU1

Do the following assignment, and multiply the Y-th stardust particle's y-coordinate with an unsigned number Q: Y1 = y_hi (A P) = |y_hi| * Q
.MLU1 LDA SY,Y ; Set Y1 the Y-th byte of SY STA Y1 ; Fall through into MLU2 to calculate: ; ; (A P) = |A| * Q
Name: MLU2 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = |A| * Q
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS1 calls MLU2 * STARS6 calls MLU2

Do the following multiplication of a sign-magnitude 8-bit number P with an unsigned number Q: (A P) = |A| * Q
.MLU2 AND #%01111111 ; Clear the sign bit in P, so P = |A| STA P ; Fall through into MULTU to calculate: ; ; (A P) = P * Q ; = |A| * Q
Name: MULTU [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = P * Q
Context: See this subroutine on its own page References: This subroutine is called as follows: * PLS3 calls MULTU * TT24 calls MULTU

Do the following multiplication of unsigned 8-bit numbers: (A P) = P * Q
.MULTU LDX Q ; Set X = Q BEQ MU1 ; If X = Q = 0, jump to MU1 to copy X into P and A, ; clear the C flag and return from the subroutine using ; a tail call ; Otherwise fall through into MU11 to set (A P) = P * X
Name: MU11 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = P * X Deep dive: Shift-and-add multiplication
Context: See this subroutine on its own page References: This subroutine is called as follows: * SQUA2 calls MU11

Do the following multiplication of two unsigned 8-bit numbers: (A P) = P * X This uses the same shift-and-add approach as MULT1, but it's simpler as we are dealing with unsigned numbers in P and X. See the deep dive on "Shift-and-add multiplication" for a discussion of how this algorithm works.
.MU11 DEX ; Set T = X - 1 STX T ; ; We subtract 1 as the C flag will be set when we want ; to do an addition in the loop below LDA #0 ; Set A = 0 so we can start building the answer in A ;LDX #8 ; This instruction is commented out in the original ; source TAX ; Copy A into X. There is a comment in the original ; source here that says "just in case", which refers to ; the MU11 routine in the cassette and disc versions, ; which set X to 0 (as they use X as a loop counter). ; The version here doesn't use a loop, but this ; instruction makes sure the unrolled version returns ; the same results as the loop versions, just in case ; something out there relies on MU11 returning X = 0 LSR P ; Set P = P >> 1 ; and C flag = bit 0 of P ; We now repeat the following four instruction block ; eight times, one for each bit in P. In the cassette ; and disc versions of Elite the following is done with ; a loop, but it is marginally faster to unroll the loop ; and have eight copies of the code, though it does take ; up a bit more memory (though that isn't a concern when ; you have a 6502 Second Processor) BCC P%+4 ; If C (i.e. bit 0 of P) is set, do the ADC T ; addition for this bit of P: ; ; A = A + T + C ; = A + X - 1 + 1 ; = A + X ROR A ; Shift A right to catch the next digit of our result, ; which the next ROR sticks into the left end of P while ; also extracting the next bit of P ROR P ; Add the overspill from shifting A to the right onto ; the start of P, and shift P right to fetch the next ; bit for the calculation into the C flag BCC P%+4 ; Repeat for the second time ADC T ROR A ROR P BCC P%+4 ; Repeat for the third time ADC T ROR A ROR P BCC P%+4 ; Repeat for the fourth time ADC T ROR A ROR P BCC P%+4 ; Repeat for the fifth time ADC T ROR A ROR P BCC P%+4 ; Repeat for the sixth time ADC T ROR A ROR P BCC P%+4 ; Repeat for the seventh time ADC T ROR A ROR P BCC P%+4 ; Repeat for the eighth time ADC T ROR A ROR P RTS ; Return from the subroutine
Name: FMLTU2 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate A = K * sin(A) Deep dive: The sine, cosine and arctan tables
Context: See this subroutine on its own page References: This subroutine is called as follows: * CIRCLE2 calls FMLTU2

Calculate the following: A = K * sin(A) Because this routine uses the sine lookup table SNE, we can also call this routine to calculate cosine multiplication. To calculate the following: A = K * cos(B) call this routine with B + 16 in the accumulator, as sin(B + 16) = cos(B).
.FMLTU2 AND #%00011111 ; Restrict A to bits 0-5 (so it's in the range 0-31) TAX ; Set Q = sin(A) * 256 LDA SNE,X STA Q LDA K ; Set A to the radius in K ; Fall through into FMLTU to do the following: ; ; (A ?) = A * Q ; = K * sin(A) * 256 ; ; which is equivalent to: ; ; A = K * sin(A)
Name: FMLTU [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate A = A * Q / 256 Deep dive: Multiplication and division using logarithms
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOEXP calls FMLTU * DrawLightning calls FMLTU * LL51 calls FMLTU * LL9 (Part 5 of 12) calls FMLTU * MVEIT (Part 3 of 9) calls FMLTU * PLS22 calls FMLTU

Do the following multiplication of two unsigned 8-bit numbers, returning only the high byte of the result: (A ?) = A * Q or, to put it another way: A = A * Q / 256 The Master and 6502 Second Processor versions use logarithms to speed up the multiplication process. See the deep dive on "Multiplication using logarithms" for more details.
Returns: C flag The C flag is clear if A = 0, or set if we return a result from one of the log tables
.FMLTU STX P ; Store X in P so we can preserve it through the call to ; FMULTU STA widget ; Store A in widget, so now widget = argument A TAX ; Transfer A into X, so now X = argument A BEQ MU3 ; If A = 0, jump to MU3 to return a result of 0, as ; 0 * Q / 256 is always 0 ; We now want to calculate La + Lq, first adding the low ; bytes (from the logL table), and then the high bytes ; (from the log table) LDA logL,X ; Set A = low byte of La ; = low byte of La (as we set X to A above) LDX Q ; Set X = Q BEQ MU3again ; If X = 0, jump to MU3again to return a result of 0, as ; A * 0 / 256 is always 0 CLC ; Set A = A + low byte of Lq ADC logL,X ; = low byte of La + low byte of Lq BMI oddlog ; If A > 127, jump to oddlog LDA log,X ; Set A = high byte of Lq LDX widget ; Set A = A + C + high byte of La ADC log,X ; = high byte of Lq + high byte of La + C ; ; so we now have: ; ; A = high byte of (La + Lq) BCC MU3again ; If the addition fitted into one byte and didn't carry, ; then La + Lq < 256, so we jump to MU3again to return a ; result of 0 and the C flag clear ; If we get here then the C flag is set, ready for when ; we return from the subroutine below TAX ; Otherwise La + Lq >= 256, so we return the A-th entry LDA antilog,X ; from the antilog table LDX P ; Restore X from P so it is preserved RTS ; Return from the subroutine .oddlog LDA log,X ; Set A = high byte of Lq LDX widget ; Set A = A + C + high byte of La ADC log,X ; = high byte of Lq + high byte of La + C ; ; so we now have: ; ; A = high byte of (La + Lq) BCC MU3again ; If the addition fitted into one byte and didn't carry, ; then La + Lq < 256, so we jump to MU3again to return a ; result of 0 and the C flag clear ; If we get here then the C flag is set, ready for when ; we return from the subroutine below TAX ; Otherwise La + Lq >= 256, so we return the A-th entry LDA antilogODD,X ; from the antilogODD table .MU3 ; If we get here then A (our result) is already 0 LDX P ; Restore X from P so it is preserved RTS ; Return from the subroutine .MU3again LDA #0 ; Set A = 0 LDX P ; Restore X from P so it is preserved RTS ; Return from the subroutine
Name: MLTU2 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P+1 P) = (A ~P) * Q Deep dive: Shift-and-add multiplication
Context: See this subroutine on its own page References: This subroutine is called as follows: * MVEIT (Part 5 of 9) calls MLTU2 * MVEIT (Part 5 of 9) calls via MLTU2-2

Do the following multiplication of an unsigned 16-bit number and an unsigned 8-bit number: (A P+1 P) = (A ~P) * Q where ~P means P EOR %11111111 (i.e. P with all its bits flipped). In other words, if you wanted to calculate $1234 * $56, you would: * Set A to $12 * Set P to $34 EOR %11111111 = $CB * Set Q to $56 before calling MLTU2. This routine is like a mash-up of MU11 and FMLTU. It uses part of FMLTU's inverted argument trick to work out whether or not to do an addition, and like MU11 it sets up a counter in X to extract bits from (P+1 P). But this time we extract 16 bits from (P+1 P), so the result is a 24-bit number. The core of the algorithm is still the shift-and-add approach explained in MULT1, just with more bits.
Returns: Q Q is preserved
Other entry points: MLTU2-2 Set Q to X, so this calculates (A P+1 P) = (A ~P) * X
STX Q ; Store X in Q .MLTU2 EOR #%11111111 ; Flip the bits in A and rotate right, storing the LSR A ; result in P+1, so we now calculate (P+1 P) * Q STA P+1 LDA #0 ; Set A = 0 so we can start building the answer in A LDX #16 ; Set up a counter in X to count the 16 bits in (P+1 P) ROR P ; Set P = P >> 1 with bit 7 = bit 0 of A ; and C flag = bit 0 of P .MUL7 BCS MU21 ; If C (i.e. the next bit from P) is set, do not do the ; addition for this bit of P, and instead skip to MU21 ; to just do the shifts ADC Q ; Do the addition for this bit of P: ; ; A = A + Q + C ; = A + Q ROR A ; Rotate (A P+1 P) to the right, so we capture the next ROR P+1 ; digit of the result in P+1, and extract the next digit ROR P ; of (P+1 P) in the C flag DEX ; Decrement the loop counter BNE MUL7 ; Loop back for the next bit until P has been rotated ; all the way RTS ; Return from the subroutine .MU21 LSR A ; Shift (A P+1 P) to the right, so we capture the next ROR P+1 ; digit of the result in P+1, and extract the next digit ROR P ; of (P+1 P) in the C flag DEX ; Decrement the loop counter BNE MUL7 ; Loop back for the next bit until P has been rotated ; all the way RTS ; Return from the subroutine
Name: MUT3 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: An unused routine that does the same as MUT2
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This routine is never actually called, but it is identical to MUT2, as the extra instructions have no effect.
.MUT3 LDX ALP1 ; Set P = ALP1, though this gets overwritten by the STX P ; following, so this has no effect ; Fall through into MUT2 to do the following: ; ; (S R) = XX(1 0) ; (A P) = Q * A
Name: MUT2 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (S R) = XX(1 0) and (A P) = Q * A
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS1 calls MUT2

Do the following assignment, and multiplication of two signed 8-bit numbers: (S R) = XX(1 0) (A P) = Q * A
.MUT2 LDX XX+1 ; Set S = XX+1 STX S ; Fall through into MUT1 to do the following: ; ; R = XX ; (A P) = Q * A
Name: MUT1 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate R = XX and (A P) = Q * A
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS6 calls MUT1

Do the following assignment, and multiplication of two signed 8-bit numbers: R = XX (A P) = Q * A
.MUT1 LDX XX ; Set R = XX STX R ; Fall through into MULT1 to do the following: ; ; (A P) = Q * A
Name: MULT1 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = Q * A Deep dive: Shift-and-add multiplication
Context: See this subroutine on its own page References: This subroutine is called as follows: * MAD calls MULT1 * MULT12 calls MULT1

Do the following multiplication of two 8-bit sign-magnitude numbers: (A P) = Q * A
.MULT1 TAX ; Store A in X 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 TXA ; The macro call overwrites A, so restore the value of A ; which we copied into X above AND #%01111111 ; Set P = |A| >> 1 LSR A ; and C flag = bit 0 of A STA P TXA ; Restore argument A EOR Q ; Set bit 7 of A and T if Q and A have different signs, AND #%10000000 ; clear bit 7 if they have the same signs, 0 all other STA T ; bits, i.e. T contains the sign bit of Q * A LDA Q ; Set A = |Q| AND #%01111111 BEQ mu10 ; If |Q| = 0 jump to mu10 (with A set to 0) TAX ; Set T1 = |Q| - 1 DEX ; STX T1 ; We subtract 1 as the C flag will be set when we want ; to do an addition in the loop below ; We are now going to work our way through the bits of ; P, and do a shift-add for any bits that are set, ; keeping the running total in A. We already set up ; the first shift at the start of this routine, as ; P = |A| >> 1 and C = bit 0 of A, so we now need to set ; up a loop to sift through the other 7 bits in P LDA #0 ; Set A = 0 so we can start building the answer in A TAX ; Copy A into X, to make sure the unrolled version ; returns the same results as the loop versions, just ; in case something out there relies on MULT1 returning ; X = 0 BCC P%+4 ; If C (i.e. the next bit from P) is set, do the ADC T1 ; addition for this bit of P: ; ; A = A + T1 + C ; = A + |Q| - 1 + 1 ; = A + |Q| ROR A ; As mentioned above, this ROR shifts A right and ; catches bit 0 in C - giving another digit for our ; result - and the next ROR sticks that bit into the ; left end of P while also extracting the next bit of P ; for the next addition ROR P ; Add the overspill from shifting A to the right onto ; the start of P, and shift P right to fetch the next ; bit for the calculation BCC P%+4 ; Repeat for the second time ADC T1 ROR A ROR P BCC P%+4 ; Repeat for the third time ADC T1 ROR A ROR P BCC P%+4 ; Repeat for the fourth time ADC T1 ROR A ROR P BCC P%+4 ; Repeat for the fifth time ADC T1 ROR A ROR P BCC P%+4 ; Repeat for the sixth time ADC T1 ROR A ROR P BCC P%+4 ; Repeat for the seventh time ADC T1 ROR A ROR P LSR A ; Rotate (A P) once more to get the final result, as ROR P ; we only pushed 7 bits through the above process ORA T ; Set the sign bit of the result that we stored in T RTS ; Return from the subroutine .mu10 STA P ; If we get here, the result is 0 and A = 0, so set ; P = 0 so (A P) = 0 RTS ; Return from the subroutine
Name: MULT12 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (S R) = Q * A
Context: See this subroutine on its own page References: This subroutine is called as follows: * TAS3 calls MULT12 * TAS4 calls MULT12 * TIDY calls MULT12 * TIS3 calls MULT12

Calculate: (S R) = Q * A
.MULT12 JSR MULT1 ; Set (A P) = Q * A STA S ; Set (S P) = (A P) ; = Q * A 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 ; Set (S R) = (S P) STA R ; = Q * A RTS ; Return from the subroutine
Name: TAS3 [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the dot product of XX15 and an orientation vector
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOCKIT calls TAS3 * TACTICS (Part 3 of 7) calls TAS3 * TACTICS (Part 7 of 7) calls TAS3

Calculate the dot product of the vector in XX15 and one of the orientation vectors, as determined by the value of Y. If vect is the orientation vector, we calculate this: (A X) = vect . XX15 = vect_x * XX15 + vect_y * XX15+1 + vect_z * XX15+2
Arguments: Y The orientation vector: * If Y = 10, calculate nosev . XX15 * If Y = 16, calculate roofv . XX15 * If Y = 22, calculate sidev . XX15
Returns: (A X) The result of the dot product
.TAS3 LDX INWK,Y ; Set Q = the Y-th byte of INWK, i.e. vect_x STX Q LDA XX15 ; Set A = XX15 JSR MULT12 ; Set (S R) = Q * A ; = vect_x * XX15 LDX INWK+2,Y ; Set Q = the Y+2-th byte of INWK, i.e. vect_y STX Q LDA XX15+1 ; Set A = XX15+1 JSR MAD ; Set (A X) = Q * A + (S R) ; = vect_y * XX15+1 + vect_x * XX15 STA S ; Set (S R) = (A X) STX R LDX INWK+4,Y ; Set Q = the Y+2-th byte of INWK, i.e. vect_z STX Q LDA XX15+2 ; Set A = XX15+2 ; Fall through into MAD to set: ; ; (A X) = Q * A + (S R) ; = vect_z * XX15+2 + vect_y * XX15+1 + ; vect_x * XX15
Name: MAD [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A X) = Q * A + (S R)
Context: See this subroutine on its own page References: This subroutine is called as follows: * MVS4 calls MAD * STARS2 calls MAD * TAS3 calls MAD * TAS4 calls MAD * TIS1 calls MAD * TIS3 calls MAD

Calculate (A X) = Q * A + (S R)
.MAD JSR MULT1 ; Call MULT1 to set (A P) = Q * A ; Fall through into ADD to do: ; ; (A X) = (A P) + (S R) ; = Q * A + (S R)
Name: ADD [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A X) = (A P) + (S R) Deep dive: Adding sign-magnitude numbers
Context: See this subroutine on its own page References: This subroutine is called as follows: * InSystemJump calls ADD * MVS5 calls ADD * PLS22 calls ADD * STARS1 calls ADD * STARS2 calls ADD * STARS6 calls ADD

Add two 16-bit sign-magnitude numbers together, calculating: (A X) = (A P) + (S R)
.ADD STA T1 ; Store argument A in T1 AND #%10000000 ; Extract the sign (bit 7) of A and store it in T STA T EOR S ; EOR bit 7 of A with S. If they have different bit 7s BMI MU8 ; (i.e. they have different signs) then bit 7 in the ; EOR result will be 1, which means the EOR result is ; negative. So the AND, EOR and BMI together mean "jump ; to MU8 if A and S have different signs" ; If we reach here, then A and S have the same sign, so ; we can add them and set the sign to get the result LDA R ; Add the least significant bytes together into X: CLC ; ADC P ; X = P + R TAX LDA S ; Add the most significant bytes together into A. We ADC T1 ; stored the original argument A in T1 earlier, so we ; can do this with: ; ; A = A + S + C ; = T1 + S + C ORA T ; If argument A was negative (and therefore S was also ; negative) then make sure result A is negative by ; OR'ing the result with the sign bit from argument A ; (which we stored in T) RTS ; Return from the subroutine .MU8 ; If we reach here, then A and S have different signs, ; so we can subtract their absolute values and set the ; sign to get the result LDA S ; Clear the sign (bit 7) in S and store the result in AND #%01111111 ; U, so U now contains |S| STA U LDA P ; Subtract the least significant bytes into X: SEC ; SBC R ; X = P - R TAX LDA T1 ; Restore the A of the argument (A P) from T1 and AND #%01111111 ; clear the sign (bit 7), so A now contains |A| SBC U ; Set A = |A| - |S| ; At this point we have |A P| - |S R| in (A X), so we ; need to check whether the subtraction above was the ; right way round (i.e. that we subtracted the smaller ; absolute value from the larger absolute value) BCS MU9 ; If |A| >= |S|, our subtraction was the right way ; round, so jump to MU9 to set the sign ; If we get here, then |A| < |S|, so our subtraction ; above was the wrong way round (we actually subtracted ; the larger absolute value from the smaller absolute ; value). So let's subtract the result we have in (A X) ; from zero, so that the subtraction is the right way ; round STA U ; Store A in U TXA ; Set X = 0 - X using two's complement (to negate a EOR #$FF ; number in two's complement, you can invert the bits ADC #1 ; and add one - and we know the C flag is clear as we TAX ; didn't take the BCS branch above, so the ADC will do ; the correct addition) LDA #0 ; Set A = 0 - A, which we can do this time using a SBC U ; subtraction with the C flag clear ORA #%10000000 ; We now set the sign bit of A, so that the EOR on the ; next line will give the result the opposite sign to ; argument A (as T contains the sign bit of argument ; A). This is the same as giving the result the same ; sign as argument S (as A and S have different signs), ; which is what we want, as S has the larger absolute ; value .MU9 EOR T ; If we get here from the BCS above, then |A| >= |S|, ; so we want to give the result the same sign as ; argument A, so if argument A was negative, we flip ; the sign of the result with an EOR (to make it ; negative) RTS ; Return from the subroutine
Name: TIS1 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A ?) = (-X * A + (S R)) / 96 Deep dive: Shift-and-subtract division
Context: See this subroutine on its own page References: This subroutine is called as follows: * TIDY calls TIS1

Calculate the following expression between sign-magnitude numbers, ignoring the low byte of the result: (A ?) = (-X * A + (S R)) / 96 This uses the same shift-and-subtract algorithm as TIS2, just with the quotient A hard-coded to 96.
Returns: Q Gets set to the value of argument X
.TIS1 STX Q ; Set Q = X EOR #%10000000 ; Flip the sign bit in A JSR MAD ; Set (A X) = Q * A + (S R) ; = X * -A + (S R) .DVID96 TAX ; Set T to the sign bit of the result AND #%10000000 STA T TXA ; Set A to the high byte of the result with the sign bit AND #%01111111 ; cleared, so (A ?) = |X * A + (S R)| ; The following is identical to TIS2, except Q is ; hard-coded to 96, so this does A = A / 96 LDX #254 ; Set T1 to have bits 1-7 set, so we can rotate through STX T1 ; 7 loop iterations, getting a 1 each time, and then ; getting a 0 on the 8th iteration... and we can also ; use T1 to catch our result bits into bit 0 each time .DVL3 ASL A ; Shift A to the left CMP #96 ; If A < 96 skip the following subtraction BCC DV4 SBC #96 ; Set A = A - 96 ; ; Going into this subtraction we know the C flag is ; set as we passed through the BCC above, and we also ; know that A >= 96, so the C flag will still be set ; once we are done .DV4 ROL T1 ; Rotate the counter in T1 to the left, and catch the ; result bit into bit 0 (which will be a 0 if we didn't ; do the subtraction, or 1 if we did) BCS DVL3 ; If we still have set bits in T1, loop back to DVL3 to ; do the next iteration of 7 LDA T1 ; Fetch the result from T1 into A ORA T ; Give A the sign of the result that we stored above RTS ; Return from the subroutine
Name: DV42 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (P R) = 256 * DELTA / z_hi
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS1 calls DV42 * STARS6 calls DV42

Calculate the following division and remainder: P = DELTA / (the Y-th stardust particle's z_hi coordinate) R = remainder as a fraction of A, where 1.0 = 255 Another way of saying the above is this: (P R) = 256 * DELTA / z_hi DELTA is a value between 1 and 40, and the minimum z_hi is 16 (dust particles are removed at lower values than this), so this means P is between 0 and 2 (as 40 / 16 = 2.5, so the maximum result is P = 2 and R = 128. This uses the same shift-and-subtract algorithm as TIS2, but this time we keep the remainder.
Arguments: Y The number of the stardust particle to process
Returns: C flag The C flag is cleared
.DV42 LDA SZ,Y ; Fetch the Y-th dust particle's z_hi coordinate into A ; Fall through into DV41 to do: ; ; (P R) = 256 * DELTA / A ; = 256 * DELTA / Y-th stardust particle's z_hi
Name: DV41 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (P R) = 256 * DELTA / A
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS2 calls DV41

Calculate the following division and remainder: P = DELTA / A R = remainder as a fraction of A, where 1.0 = 255 Another way of saying the above is this: (P R) = 256 * DELTA / A This uses the same shift-and-subtract algorithm as TIS2, but this time we keep the remainder.
Returns: C flag The C flag is cleared
.DV41 STA Q ; Store A in Q LDA DELTA ; Fetch the speed from DELTA into A ; Fall through into DVID4 to do: ; ; (P R) = 256 * A / Q ; = 256 * DELTA / A
Name: DVID4 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (P R) = 256 * A / Q Deep dive: Shift-and-subtract division
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOEXP calls DVID4

Calculate the following division and remainder: P = A / Q R = remainder as a fraction of Q, where 1.0 = 255 Another way of saying the above is this: (P R) = 256 * A / Q This uses the same shift-and-subtract algorithm as TIS2, but this time we keep the remainder and the loop is unrolled.
Returns: C flag The C flag is cleared
.DVID4 ASL A ; Shift A left and store in P (we will build the result STA P ; in P) LDA #0 ; Set A = 0 for us to build a remainder ; We now repeat the following five instruction block ; eight times, one for each bit in P. In the cassette ; and disc versions of Elite the following is done with ; a loop, but it is marginally faster to unroll the loop ; and have eight copies of the code, though it does take ; up a bit more memory (though that isn't a concern when ; you have a 6502 Second Processor) ROL A ; Shift A to the left CMP Q ; If A < Q skip the following subtraction BCC P%+4 SBC Q ; A >= Q, so set A = A - Q ROL P ; Shift P to the left, pulling the C flag into bit 0 ROL A ; Repeat for the second time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the third time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the fourth time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the fifth time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the sixth time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the seventh time CMP Q BCC P%+4 SBC Q ROL P ROL A ; Repeat for the eighth time CMP Q BCC P%+4 SBC Q ROL P LDX #0 ; Set X = 0 so this unrolled version of DVID4 also ; returns X = 0 STA widget ; This contains the code from the LL28+4 routine, so TAX ; this section is exactly equivalent to a JMP LL28+4 BEQ LLfix22 ; call, but is slightly faster as it's been inlined LDA logL,X ; (so it converts the remainder in A into an integer LDX Q ; representation of the fractional value A / Q, in R, SEC ; where 1.0 = 255, and it also clears the C flag SBC logL,X BMI noddlog22 LDX widget LDA log,X LDX Q SBC log,X BCS LL222 TAX LDA antilog,X .LLfix22 STA R ; This is also part of the inline LL28+4 routine RTS .LL222 LDA #255 ; This is also part of the inline LL28+4 routine STA R RTS .noddlog22 LDX widget ; This is also part of the inline LL28+4 routine LDA log,X LDX Q SBC log,X BCS LL222 TAX LDA antilogODD,X STA R RTS
Name: DVID3B2 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_lo) Deep dive: Shift-and-subtract division
Context: See this subroutine on its own page References: This subroutine is called as follows: * PLANET calls DVID3B2 * PLS1 calls DVID3B2 * PLS6 calls DVID3B2

Calculate the following: K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_lo) The actual division here is done as an 8-bit calculation using LL31, but this routine shifts both the numerator (the top part of the division) and the denominator (the bottom part of the division) around to get the multi-byte result we want. Specifically, it shifts both of them to the left as far as possible, keeping a tally of how many shifts get done in each one - and specifically, the difference in the number of shifts between the top and bottom (as shifting both of them once in the same direction won't change the result). It then divides the two highest bytes with the simple 8-bit routine in LL31, and shifts the result by the difference in the number of shifts, which acts as a scale factor to get the correct result.
Returns: K(3 2 1 0) The result of the division X X is preserved
.DVID3B2 STA P+2 ; Set P+2 = A LDA INWK+6 ; Set Q = z_lo, making sure Q is at least 1 ORA #1 STA Q LDA INWK+7 ; Set R = z_hi STA R LDA INWK+8 ; Set S = z_sign STA S .DVID3B 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 ; Given the above assignments, we now want to calculate ; the following to get the result we want: ; ; K(3 2 1 0) = P(2 1 0) / (S R Q) LDA P ; Make sure P(2 1 0) is at least 1 ORA #1 STA P LDA P+2 ; Set T to the sign of P+2 * S (i.e. the sign of the EOR S ; result) and store it in T AND #%10000000 STA T LDY #0 ; Set Y = 0 to store the scale factor LDA P+2 ; Clear the sign bit of P+2, so the division can be done AND #%01111111 ; with positive numbers and we'll set the correct sign ; below, once all the maths is done ; ; This also leaves A = P+2, which we use below .DVL9 ; We now shift (A P+1 P) left until A >= 64, counting ; the number of shifts in Y. This makes the top part of ; the division as large as possible, thus retaining as ; much accuracy as we can. When we come to return the ; final result, we shift the result by the number of ; places in Y, and in the correct direction CMP #64 ; If A >= 64, jump down to DV14 BCS DV14 ASL P ; Shift (A P+1 P) to the left ROL P+1 ROL A INY ; Increment the scale factor in Y BNE DVL9 ; Loop up to DVL9 (this BNE is effectively a JMP, as Y ; will never be zero) .DV14 ; If we get here, A >= 64 and contains the highest byte ; of the numerator, scaled up by the number of left ; shifts in Y STA P+2 ; Store A in P+2, so we now have the scaled value of ; the numerator in P(2 1 0) LDA S ; Set A = |S| AND #%01111111 .DVL6 ; We now shift (S R Q) left until bit 7 of S is set, ; reducing Y by the number of shifts. This makes the ; bottom part of the division as large as possible, thus ; retaining as much accuracy as we can. When we come to ; return the final result, we shift the result by the ; total number of places in Y, and in the correct ; direction, to give us the correct result ; ; We set A to |S| above, so the following actually ; shifts (A R Q) DEY ; Decrement the scale factor in Y ASL Q ; Shift (A R Q) to the left ROL R ROL A BPL DVL6 ; Loop up to DVL6 to do another shift, until bit 7 of A ; is set and we can't shift left any further .DV9 ; We have now shifted both the numerator and denominator ; left as far as they will go, keeping a tally of the ; overall scale factor of the various shifts in Y. We ; can now divide just the two highest bytes to get our ; result STA Q ; Set Q = A, the highest byte of the denominator 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 #254 ; Set R to have bits 1-7 set, so we can pass this to STA R ; LL31 to act as the bit counter in the division LDA P+2 ; Set A to the highest byte of the numerator .LL31new ASL A ; This contains the code from the LL31 routine, so BCS LL29new ; this section is exactly equivalent to a JSR LL31 CMP Q ; call, but is slightly faster as it's been inlined, BCC P%+4 ; so it calculates: SBC Q ; ROL R ; R = 256 * A / Q BCS LL31new ; = 256 * numerator / denominator JMP LL312new .LL29new SBC Q ; This is also part of the inline LL31 routine SEC ROL R BCS LL31new LDA R .LL312new ; The result of our division is now in R, so we just ; need to shift it back by the scale factor in Y LDA #0 ; Set K(3 2 1) = 0 to hold the result (we populate K STA K+1 ; next) STA K+2 STA K+3 TYA ; If Y is positive, jump to DV12 BPL DV12 ; If we get here then Y is negative, so we need to shift ; the result R to the left by Y places, and then set the ; correct sign for the result LDA R ; Set A = R .DVL8 ASL A ; Shift (K+3 K+2 K+1 A) left ROL K+1 ROL K+2 ROL K+3 INY ; Increment the scale factor in Y BNE DVL8 ; Loop back to DVL8 until we have shifted left by Y ; places STA K ; Store A in K so the result is now in K(3 2 1 0) LDA K+3 ; Set K+3 to the sign in T, which we set above to the ORA T ; correct sign for the result STA K+3 RTS ; Return from the subroutine .DV13 ; If we get here then Y is zero, so we don't need to ; shift the result R, we just need to set the correct ; sign for the result LDA R ; Store R in K so the result is now in K(3 2 1 0) STA K LDA T ; Set K+3 to the sign in T, which we set above to the STA K+3 ; correct sign for the result RTS ; Return from the subroutine .DV12 BEQ DV13 ; We jumped here having set A to the scale factor in Y, ; so this jumps up to DV13 if Y = 0 ; If we get here then Y is positive and non-zero, so we ; need to shift the result R to the right by Y places ; and then set the correct sign for the result. We also ; know that K(3 2 1) will stay 0, as we are shifting the ; lowest byte to the right, so no set bits will make ; their way into the top three bytes LDA R ; Set A = R .DVL10 LSR A ; Shift A right DEY ; Decrement the scale factor in Y BNE DVL10 ; Loop back to DVL10 until we have shifted right by Y ; places STA K ; Store the shifted A in K so the result is now in ; K(3 2 1 0) LDA T ; Set K+3 to the sign in T, which we set above to the STA K+3 ; correct sign for the result RTS ; Return from the subroutine
Name: cntr [Show more] Type: Subroutine Category: Dashboard Summary: Apply damping to the pitch or roll dashboard indicator
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 2 of 16) calls cntr

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 amount to dampen by X The value to dampen
Returns: X The dampened value
.cntr1 LDX #128 ; Set X = 128 to return so we don't dampen past the ; middle of the indicator .cntr2 RTS ; Return from the subroutine .cntr STA T ; Store the argument A in T LDA auto ; If the docking computer is currently activated, jump BNE cntr3 ; to cntr3 to skip the following as we always want to ; enable damping for the docking computer LDA DAMP ; If DAMP is zero, then damping is disabled, so jump to BEQ cntr2 ; cntr2 to return from the subroutine .cntr3 TXA ; If X >= 128, then it's in the right-hand side of the BMI cntr4 ; dashboard slider, so jump to cntr4 to decrement it by ; T to move it closer to the centre ; If we get here then the current value in X is in the ; left-hand side of the dashboard slider, so now we ; increment it by T to move it closer to the centre CLC ; Set A = A + T ADC T BMI cntr1 ; If the addition pushed A to 128 or higher, jump to ; cntr1 to return a value of X = 128, so we don't dampen ; past the middle of the indicator TAX ; Set X to the newly dampened value RTS ; Return from the subroutine .cntr4 SEC ; Set A = A - T SBC T BPL cntr1 ; If the subtraction reduced A to 127 or lower, jump to ; cntr1 to return a value of X = 128, so we don't dampen ; past the middle of the indicator TAX ; Set X to the newly dampened value RTS ; Return from the subroutine
Name: BUMP2 [Show more] Type: Subroutine Category: Dashboard Summary: Bump up the value of the pitch or roll dashboard indicator
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOKEY calls BUMP2 * REDU2 calls via RE2+2

Increase ("bump up") X by A, where X is either the current rate of pitch or the current rate of roll. The rate of pitch or roll ranges from 1 to 255 with 128 as the centre point. This is the amount by which the pitch or roll is currently changing, so 1 means it is decreasing at the maximum rate, 128 means it is not changing, and 255 means it is increasing at the maximum rate. These values correspond to the line on the DC or RL indicators on the dashboard, with 1 meaning full left, 128 meaning the middle, and 255 meaning full right. If bumping up X would push it past 255, then X is set to 255. If keyboard auto-recentre is configured and the result is less than 128, we bump X up to the mid-point, 128. This is the equivalent of having a roll or pitch in the left half of the indicator, when increasing the roll or pitch should jump us straight to the mid-point.
Other entry points: RE2+2 Restore A from T and return from the subroutine
.BUMP2 STA T ; Store argument A in T so we can restore it later TXA ; Copy argument X into A CLC ; Clear the C flag so we can do addition without the ; C flag affecting the result ADC T ; Set X = A = argument X + argument A TAX BCC RE2 ; If the C flag is clear, then we didn't overflow, so ; jump to RE2 to auto-recentre and return the result LDX #255 ; We have an overflow, so set X to the maximum possible ; value of 255 .RE2 BPL djd1 ; If X has bit 7 clear (i.e. the result < 128), then ; jump to djd1 in routine REDU2 to do an auto-recentre, ; if configured, because the result is on the left side ; of the centre point of 128 ; Jumps to RE2+2 end up here LDA T ; Restore the original argument A from T into A RTS ; Return from the subroutine
Name: REDU2 [Show more] Type: Subroutine Category: Dashboard Summary: Reduce the value of the pitch or roll dashboard indicator
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOKEY calls REDU2 * BUMP2 calls via djd1

Reduce X by A, where X is either the current rate of pitch or the current rate of roll. The rate of pitch or roll ranges from 1 to 255 with 128 as the centre point. This is the amount by which the pitch or roll is currently changing, so 1 means it is decreasing at the maximum rate, 128 means it is not changing, and 255 means it is increasing at the maximum rate. These values correspond to the line on the DC or RL indicators on the dashboard, with 1 meaning full left, 128 meaning the middle, and 255 meaning full right. If reducing X would bring it below 1, then X is set to 1. If keyboard auto-recentre is configured and the result is greater than 128, we reduce X down to the mid-point, 128. This is the equivalent of having a roll or pitch in the right half of the indicator, when decreasing the roll or pitch should jump us straight to the mid-point.
Other entry points: djd1 Auto-recentre the value in X, if keyboard auto-recentre is configured
.REDU2 STA T ; Store argument A in T so we can restore it later TXA ; Copy argument X into A SEC ; Set the C flag so we can do subtraction without the ; C flag affecting the result SBC T ; Set X = A = argument X - argument A TAX BCS RE3 ; If the C flag is set, then we didn't underflow, so ; jump to RE3 to auto-recentre and return the result LDX #1 ; We have an underflow, so set X to the minimum possible ; value, 1 .RE3 BPL RE2+2 ; If X has bit 7 clear (i.e. the result < 128), then ; jump to RE2+2 above to return the result as is, ; because the result is on the left side of the centre ; point of 128, so we don't need to auto-centre .djd1 LDX #128 ; If we get here then keyboard auto-recentre is enabled, ; so set X to 128 (the middle of our range) LDA T ; Restore the value of A that we passed to the routine RTS ; Return from the subroutine
Name: LL5 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate Q = SQRT(R Q) Deep dive: Calculating square roots
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckAltitude calls LL5 * HAS1 calls LL5 * NORM calls LL5 * PLFL calls LL5 * TT111 calls LL5

Calculate the following square root: Q = SQRT(R Q)
.LL5 LDY R ; Set (Y S) = (R Q) LDA Q STA S ; So now to calculate Q = SQRT(Y S) LDX #0 ; Set X = 0, to hold the remainder STX Q ; Set Q = 0, to hold the result LDA #8 ; Set T = 8, to use as a loop counter STA T .LL6 CPX Q ; If X < Q, jump to LL7 BCC LL7 BNE P%+6 ; If X > Q, skip the next two instructions CPY #64 ; If Y < 64, jump to LL7 with the C flag clear, BCC LL7 ; otherwise fall through into LL8 with the C flag set TYA ; Set Y = Y - 64 SBC #64 ; TAY ; This subtraction will work as we know C is set from ; the BCC above, and the result will not underflow as we ; already checked that Y >= 64, so the C flag is also ; set for the next subtraction TXA ; Set X = X - Q SBC Q TAX .LL7 ROL Q ; Shift the result in Q to the left, shifting the C flag ; into bit 0 and bit 7 into the C flag ASL S ; Shift the dividend in (Y S) to the left, inserting TYA ; bit 7 from above into bit 0 ROL A TAY TXA ; Shift the remainder in X to the left ROL A TAX ASL S ; Shift the dividend in (Y S) to the left TYA ROL A TAY TXA ; Shift the remainder in X to the left ROL A TAX DEC T ; Decrement the loop counter BNE LL6 ; Loop back to LL6 until we have done 8 loops RTS ; Return from the subroutine
Name: LL28 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate R = 256 * A / Q Deep dive: Multiplication and division using logarithms
Context: See this subroutine on its own page References: This subroutine is called as follows: * ARCTAN calls LL28 * GetScrollDivisions calls LL28 * LAUN calls LL28 * LL145 (Part 3 of 4) calls LL28 * LL164 calls LL28 * LL61 calls LL28 * LL9 (Part 3 of 12) calls LL28 * LL9 (Part 8 of 12) calls LL28 * ProjectScrollText calls LL28

Calculate the following, where A < Q: R = 256 * A / Q This is a sister routine to LL61, which does the division when A >= Q. If A >= Q then 255 is returned and the C flag is set to indicate an overflow (the C flag is clear if the division was a success). The result is returned in one byte as the result of the division multiplied by 256, so we can return fractional results using integers. This routine uses the same logarithm algorithm that's documented in FMLTU, except it subtracts the logarithm values, to do a division instead of a multiplication.
Returns: C flag Set if the answer is too big for one byte, clear if the division was a success
Other entry points: LL28+4 Skips the A >= Q check and always returns with C flag cleared, so this can be called if we know the division will work
.LL2 LDA #255 ; The division is very close to 1, so return the closest STA R ; possible answer to 256, i.e. R = 255 RTS ; Return from the subroutine .LL28 CMP Q ; If A >= Q, then the answer will not fit in one byte, BCS LL2 ; so jump to LL2 to return 255 STA widget ; Store A in widget, so now widget = argument A TAX ; Transfer A into X, so now X = argument A BEQ LLfix ; If A = 0, jump to LLfix to return a result of 0, as ; 0 * Q / 256 is always 0 ; We now want to calculate log(A) - log(Q), first adding ; the low bytes (from the logL table), and then the high ; bytes (from the log table) LDA logL,X ; Set A = low byte of log(X) ; = low byte of log(A) (as we set X to A above) LDX Q ; Set X = Q SEC ; Set A = A - low byte of log(Q) SBC logL,X ; = low byte of log(A) - low byte of log(Q) BMI noddlog ; If the subtraction is negative, jump to noddlog LDX widget ; Set A = high byte of log(A) - high byte of log(Q) LDA log,X LDX Q SBC log,X BCS LL2 ; If the subtraction fitted into one byte and didn't ; underflow, then log(A) - log(Q) < 256, so we jump to ; LL2 return a result of 255 TAX ; Otherwise we return the A-th entry from the antilog LDA antilog,X ; table .LLfix STA R ; Set the result in R to the value of A RTS ; Return from the subroutine .noddlog LDX widget ; Set A = high byte of log(A) - high byte of log(Q) LDA log,X LDX Q SBC log,X BCS LL2 ; If the subtraction fitted into one byte and didn't ; underflow, then log(A) - log(Q) < 256, so we jump to ; LL2 to return a result of 255 TAX ; Otherwise we return the A-th entry from the antilogODD LDA antilogODD,X ; table STA R ; Set the result in R to the value of A RTS ; Return from the subroutine
Name: TIS2 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate A = A / Q Deep dive: Shift-and-subtract division
Context: See this subroutine on its own page References: This subroutine is called as follows: * NORM calls TIS2

Calculate the following division, where A is a sign-magnitude number and Q is a positive integer: A = A / Q The value of A is returned as a sign-magnitude number with 96 representing 1, and the maximum value returned is 1 (i.e. 96). This routine is used when normalising vectors, where we represent fractions using integers, so this gives us an approximation to two decimal places.
.TIS2 TAY ; Store the argument A in Y AND #%01111111 ; Strip the sign bit from the argument, so A = |A| CMP Q ; If A >= Q then jump to TI4 to return a 1 with the BCS TI4 ; correct sign LDX #%11111110 ; Set T to have bits 1-7 set, so we can rotate through 7 STX T ; loop iterations, getting a 1 each time, and then ; getting a 0 on the 8th iteration... and we can also ; use T to catch our result bits into bit 0 each time .TIL2 ASL A ; Shift A to the left CMP Q ; If A < Q skip the following subtraction BCC P%+4 SBC Q ; A >= Q, so set A = A - Q ; ; Going into this subtraction we know the C flag is ; set as we passed through the BCC above, and we also ; know that A >= Q, so the C flag will still be set once ; we are done ROL T ; Rotate the counter in T to the left, and catch the ; result bit into bit 0 (which will be a 0 if we didn't ; do the subtraction, or 1 if we did) BCS TIL2 ; If we still have set bits in T, loop back to TIL2 to ; do the next iteration of 7 ; We've done the division and now have a result in the ; range 0-255 here, which we need to reduce to the range ; 0-96. We can do that by multiplying the result by 3/8, ; as 256 * 3/8 = 96 LDA T ; Set T = T / 4 LSR A LSR A STA T LSR A ; Set T = T / 8 + T / 4 ADC T ; = 3T / 8 STA T TYA ; Fetch the sign bit of the original argument A AND #%10000000 ORA T ; Apply the sign bit to T RTS ; Return from the subroutine .TI4 TYA ; Fetch the sign bit of the original argument A AND #%10000000 ORA #96 ; Apply the sign bit to 96 (which represents 1) RTS ; Return from the subroutine
Name: NORM [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Normalise the three-coordinate vector in XX15 Deep dive: Tidying orthonormal vectors Orientation vectors
Context: See this subroutine on its own page References: This subroutine is called as follows: * TAS2 calls NORM * TIDY calls NORM

We do this by dividing each of the three coordinates by the length of the vector, which we can calculate using Pythagoras. Once normalised, 96 ($60) is used to represent a value of 1, and 96 with bit 7 set ($E0) is used to represent -1. This enables us to represent fractional values of less than 1 using integers.
Arguments: XX15 The vector to normalise, with: * The x-coordinate in XX15 * The y-coordinate in XX15+1 * The z-coordinate in XX15+2
Returns: XX15 The normalised vector Q The length of the original XX15 vector
Other entry points: NO1 Contains an RTS
.NORM 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 XX15 ; Fetch the x-coordinate into A JSR SQUA ; Set (A P) = A * A = x^2 STA R ; Set (R Q) = (A P) = x^2 LDA P STA Q LDA XX15+1 ; Fetch the y-coordinate into A JSR SQUA ; Set (A P) = A * A = y^2 STA T ; Set (T P) = (A P) = y^2 LDA P ; Set (R Q) = (R Q) + (T P) = x^2 + y^2 ADC Q ; STA Q ; First, doing the low bytes, Q = Q + P LDA T ; And then the high bytes, R = R + T ADC R STA R LDA XX15+2 ; Fetch the z-coordinate into A JSR SQUA ; Set (A P) = A * A = z^2 STA T ; Set (T P) = (A P) = z^2 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 (though this isn't needed, as the ; SETUP_PPU_FOR_ICON_BAR does this for us) LDA P ; Set (R Q) = (R Q) + (T P) = x^2 + y^2 + z^2 ADC Q ; STA Q ; First, doing the low bytes, Q = Q + P LDA T ; And then the high bytes, R = R + T ADC R ; BCS norm2 ; Jumping to norm2 if the addition overflows STA R JSR LL5 ; We now have the following: ; ; (R Q) = x^2 + y^2 + z^2 ; ; so we can call LL5 to use Pythagoras to get: ; ; Q = SQRT(R Q) ; = SQRT(x^2 + y^2 + z^2) ; ; So Q now contains the length of the vector (x, y, z), ; and we can normalise the vector by dividing each of ; the coordinates by this value, which we do by calling ; routine TIS2. TIS2 returns the divided figure, using ; 96 to represent 1 and 96 with bit 7 set for -1 .norm1 LDA XX15 ; Call TIS2 to divide the x-coordinate in XX15 by Q, JSR TIS2 ; with 1 being represented by 96 STA XX15 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 XX15+1 ; Call TIS2 to divide the y-coordinate in XX15+1 by Q, JSR TIS2 ; with 1 being represented by 96 STA XX15+1 LDA XX15+2 ; Call TIS2 to divide the z-coordinate in XX15+2 by Q, JSR TIS2 ; with 1 being represented by 96 STA XX15+2 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 .NO1 RTS ; Return from the subroutine .norm2 ; If we get here then the addition overflowed during the ; calculation of R = R + T above, so we need to scale ; (A Q) down before we can call LL5, and then scale the ; result up afterwards ; ; As we are calculating a square root, we can do this by ; scaling the argument in (R Q) down by a factor of 2*2, ; and then scale the result in SQRT(R Q) up by a factor ; of 2, thus side-stepping the overflow ROR A ; Set (A Q) = (A Q) / 4 ROR Q LSR A ROR Q STA R ; Set (R Q) = (A Q) JSR LL5 ; We now have the following (scaled by a factor of 4): ; ; (R Q) = x^2 + y^2 + z^2 ; ; so we can call LL5 to use Pythagoras to get: ; ; Q = SQRT(R Q) ; = SQRT(x^2 + y^2 + z^2) ; ; So Q now contains the length of the vector (x, y, z), ; and we can normalise the vector by dividing each of ; the coordinates by this value, which we do by calling ; routine TIS2. TIS2 returns the divided figure, using ; 96 to represent 1 and 96 with bit 7 set for -1 ASL Q ; Set Q = Q * 2, to scale the result back up JMP norm1 ; Jump back to norm1 to continue the calculation
Name: SetupMMC1 [Show more] Type: Subroutine Category: Utility routines Summary: Configure the MMC1 mapper and page ROM bank 0 into memory at $8000 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ResetVariables calls SetupMMC1
.SetupMMC1 LDA #%00001110 ; Set the MMC1 Control register (which is mapped to STA $9FFF ; $8000-$9FFF) as follows: LSR A ; STA $9FFF ; * Bit 0 clear, Bit 1 set = Vertical mirroring (which LSR A ; overrides the horizontal mirroring set in the iNES STA $9FFF ; header) LSR A ; STA $9FFF ; * Bits 2,3 set = PRG-ROM bank mode 3 = fix ROM bank LSR A ; 7 at $C000 and switch 16K ROM banks at $8000 STA $9FFF ; ; * Bit 4 clear = CHR-ROM bank mode 0 = switch 8K at ; a time LDA #0 ; Set the MMC1 CHR bank 0 register (which is mapped to STA $BFFF ; $A000-$BFFF) to map the first 4K of CHR-RAM to $0000 LSR A ; in the PPU, so pattern table 0 is writable RAM STA $BFFF LSR A STA $BFFF LSR A STA $BFFF LSR A STA $BFFF LDA #0 ; Set the MMC1 CHR bank 1 register (which is mapped to STA $DFFF ; $C000-$DFFF) to map the second 4K of CHR-RAM to $1000 LSR A ; in the PPU, so pattern table 1 is writable RAM STA $DFFF LSR A STA $DFFF LSR A STA $DFFF LSR A STA $DFFF JMP SetBank0 ; Page ROM bank 0 into memory at $8000, returning from ; the subroutine using a tail call IF _NTSC EQUB $F5, $F5, $F5 ; These bytes appear to be unused EQUB $F5, $F6, $F6 EQUB $F6, $F6, $F7 EQUB $F7, $F7, $F7 EQUB $F7, $F8, $F8 EQUB $F8, $F8, $F9 EQUB $F9, $F9, $F9 EQUB $F9, $FA, $FA EQUB $FA, $FA, $FA EQUB $FB, $FB, $FB EQUB $FB, $FB, $FC EQUB $FC, $FC, $FC EQUB $FC, $FD, $FD EQUB $FD, $FD, $FD EQUB $FD, $FE, $FE EQUB $FE, $FE, $FE EQUB $FF, $FF, $FF EQUB $FF, $FF ELIF _PAL EQUB $FF, $FF, $FF ; These bytes appear to be unused EQUB $FF, $FF, $FF EQUB $FF, $FF, $FF EQUB $FF, $FF, $FF EQUB $FF, $FF, $FF EQUB $FF, $FF, $FF EQUB $FF, $FF, $FF EQUB $FF ENDIF
Name: lineImage [Show more] Type: Variable Category: Drawing lines Summary: Image data for the horizontal line, vertical line and block images Deep dive: Drawing lines in the NES version
Context: See this variable on its own page References: This variable is used as follows: * SetLinePatterns uses lineImage

You can view the tiles that make up the line images here: https://www.bbcelite.com/images/source/nes/lineImage_ram.png
.lineImage EQUB $FF, $00, $00, $00, $00, $00, $00, $00 EQUB $00, $FF, $00, $00, $00, $00, $00, $00 EQUB $00, $00, $FF, $00, $00, $00, $00, $00 EQUB $00, $00, $00, $FF, $00, $00, $00, $00 EQUB $00, $00, $00, $00, $FF, $00, $00, $00 EQUB $00, $00, $00, $00, $00, $FF, $00, $00 EQUB $00, $00, $00, $00, $00, $00, $FF, $00 EQUB $00, $00, $00, $00, $00, $00, $00, $FF EQUB $00, $00, $00, $00, $00, $00, $FF, $FF EQUB $00, $00, $00, $00, $00, $FF, $FF, $FF EQUB $00, $00, $00, $00, $FF, $FF, $FF, $FF EQUB $00, $00, $00, $FF, $FF, $FF, $FF, $FF EQUB $00, $00, $FF, $FF, $FF, $FF, $FF, $FF EQUB $00, $FF, $FF, $FF, $FF, $FF, $FF, $FF EQUB $FF, $FF, $FF, $FF, $FF, $FF, $FF, $FF EQUB $80, $80, $80, $80, $80, $80, $80, $80 EQUB $40, $40, $40, $40, $40, $40, $40, $40 EQUB $20, $20, $20, $20, $20, $20, $20, $20 EQUB $10, $10, $10, $10, $10, $10, $10, $10 EQUB $08, $08, $08, $08, $08, $08, $08, $08 EQUB $04, $04, $04, $04, $04, $04, $04, $04 EQUB $02, $02, $02, $02, $02, $02, $02, $02 EQUB $01, $01, $01, $01, $01, $01, $01, $01 EQUB $00, $00, $00, $00, $00, $FF, $FF, $FF EQUB $FF, $FF, $FF, $00, $00, $00, $00, $00 EQUB $00, $00, $00, $00, $00, $C0, $C0, $C0 EQUB $C0, $C0, $C0, $00, $00, $00, $00, $00 EQUB $00, $00, $00, $00, $00, $03, $03, $03 EQUB $03, $03, $03, $00, $00, $00, $00, $00
Name: fontImage [Show more] Type: Variable Category: Text Summary: Image data for the text font Deep dive: Multi-language support in NES Elite Fonts in NES Elite
Context: See this variable on its own page References: This variable is used as follows: * CHPR (Part 3 of 6) uses fontImage * LoadHighFont uses fontImage * LoadNormalFont uses fontImage * SendViewToPPU uses fontImage

You can view the font image here: https://www.bbcelite.com/images/source/nes/fontImage_ram.png
.fontImage EQUB $00, $00, $00, $00, $00, $00, $00, $00 EQUB $30, $30, $30, $30, $00, $30, $30, $00 EQUB $7F, $63, $63, $63, $7F, $63, $63, $00 EQUB $7F, $63, $63, $63, $63, $63, $7F, $00 EQUB $78, $1E, $7F, $03, $7F, $63, $7F, $00 EQUB $1F, $78, $7F, $63, $7F, $60, $7F, $00 EQUB $7C, $CC, $78, $38, $6D, $C6, $7F, $00 EQUB $30, $30, $30, $00, $00, $00, $00, $00 EQUB $06, $0C, $18, $18, $18, $0C, $06, $00 EQUB $60, $30, $18, $18, $18, $30, $60, $00 EQUB $78, $1E, $7F, $63, $7F, $60, $7F, $00 EQUB $1C, $36, $7F, $63, $7F, $60, $7F, $00 EQUB $00, $00, $00, $00, $00, $30, $30, $60 EQUB $00, $00, $00, $7E, $00, $00, $00, $00 EQUB $00, $00, $00, $00, $00, $30, $30, $00 EQUB $1C, $36, $7F, $63, $63, $63, $7F, $00 EQUB $7F, $63, $63, $63, $63, $63, $7F, $00 EQUB $1C, $0C, $0C, $0C, $0C, $0C, $3F, $00 EQUB $7F, $03, $03, $7F, $60, $60, $7F, $00 EQUB $7F, $03, $03, $3F, $03, $03, $7F, $00 EQUB $60, $60, $66, $66, $7F, $06, $06, $00 EQUB $7F, $60, $60, $7F, $03, $03, $7F, $00 EQUB $7F, $60, $60, $7F, $63, $63, $7F, $00 EQUB $7F, $03, $03, $07, $03, $03, $03, $00 EQUB $7F, $63, $63, $7F, $63, $63, $7F, $00 EQUB $7F, $63, $63, $7F, $03, $03, $7F, $00 EQUB $00, $00, $30, $30, $00, $30, $30, $00 EQUB $00, $00, $7E, $66, $7F, $63, $7F, $60 EQUB $7F, $60, $60, $7E, $60, $60, $7F, $00 EQUB $7F, $60, $60, $7E, $60, $60, $7F, $00 EQUB $18, $0C, $06, $03, $06, $0C, $18, $00 EQUB $7F, $03, $1F, $18, $00, $18, $18, $00 EQUB $7F, $60, $60, $60, $60, $7F, $0C, $3C EQUB $7F, $63, $63, $63, $7F, $63, $63, $00 EQUB $7E, $66, $66, $7F, $63, $63, $7F, $00 EQUB $7F, $60, $60, $60, $60, $60, $7F, $00 EQUB $7F, $33, $33, $33, $33, $33, $7F, $00 EQUB $7F, $60, $60, $7E, $60, $60, $7F, $00 EQUB $7F, $60, $60, $7E, $60, $60, $60, $00 EQUB $7F, $60, $60, $60, $63, $63, $7F, $00 EQUB $63, $63, $63, $7F, $63, $63, $63, $00 EQUB $3F, $0C, $0C, $0C, $0C, $0C, $3F, $00 EQUB $7F, $0C, $0C, $0C, $0C, $0C, $7C, $00 EQUB $66, $66, $66, $7F, $63, $63, $63, $00 EQUB $60, $60, $60, $60, $60, $60, $7F, $00 EQUB $63, $77, $7F, $6B, $63, $63, $63, $00 EQUB $63, $73, $7B, $6F, $67, $63, $63, $00 EQUB $7F, $63, $63, $63, $63, $63, $7F, $00 EQUB $7F, $63, $63, $7F, $60, $60, $60, $00 EQUB $7F, $63, $63, $63, $63, $67, $7F, $03 EQUB $7F, $63, $63, $7F, $66, $66, $66, $00 EQUB $7F, $60, $60, $7F, $03, $03, $7F, $00 EQUB $7E, $18, $18, $18, $18, $18, $18, $00 EQUB $63, $63, $63, $63, $63, $63, $7F, $00 EQUB $63, $63, $66, $6C, $78, $70, $60, $00 EQUB $63, $63, $63, $6B, $7F, $77, $63, $00 EQUB $63, $36, $1C, $1C, $1C, $36, $63, $00 EQUB $63, $33, $1B, $0F, $07, $03, $03, $00 EQUB $7F, $06, $0C, $18, $30, $60, $7F, $00 EQUB $63, $3E, $63, $63, $7F, $63, $63, $00 EQUB $63, $3E, $63, $63, $63, $63, $7F, $00 EQUB $63, $00, $63, $63, $63, $63, $7F, $00 EQUB $7E, $66, $66, $7F, $63, $63, $7F, $60 EQUB $7F, $60, $60, $7E, $60, $60, $7F, $00 EQUB $00, $00, $7F, $60, $60, $7F, $0C, $3C EQUB $00, $00, $7F, $03, $7F, $63, $7F, $00 EQUB $60, $60, $7F, $63, $63, $63, $7F, $00 EQUB $00, $00, $7F, $60, $60, $60, $7F, $00 EQUB $03, $03, $7F, $63, $63, $63, $7F, $00 EQUB $00, $00, $7F, $63, $7F, $60, $7F, $00 EQUB $3F, $30, $30, $7C, $30, $30, $30, $00 EQUB $00, $00, $7F, $63, $63, $7F, $03, $7F EQUB $60, $60, $7F, $63, $63, $63, $63, $00 EQUB $18, $00, $78, $18, $18, $18, $7E, $00 EQUB $0C, $00, $3C, $0C, $0C, $0C, $0C, $7C EQUB $60, $60, $66, $66, $7F, $63, $63, $00 EQUB $78, $18, $18, $18, $18, $18, $7E, $00 EQUB $00, $00, $77, $7F, $6B, $63, $63, $00 EQUB $00, $00, $7F, $63, $63, $63, $63, $00 EQUB $00, $00, $7F, $63, $63, $63, $7F, $00 EQUB $00, $00, $7F, $63, $63, $7F, $60, $60 EQUB $00, $00, $7F, $63, $63, $7F, $03, $03 EQUB $00, $00, $7F, $60, $60, $60, $60, $00 EQUB $00, $00, $7F, $60, $7F, $03, $7F, $00 EQUB $30, $30, $7C, $30, $30, $30, $3F, $00 EQUB $00, $00, $63, $63, $63, $63, $7F, $00 EQUB $00, $00, $63, $66, $6C, $78, $70, $00 EQUB $00, $00, $63, $63, $6B, $7F, $7F, $00 EQUB $00, $00, $63, $36, $1C, $36, $63, $00 EQUB $00, $00, $63, $63, $63, $7F, $03, $7F EQUB $00, $00, $7F, $0C, $18, $30, $7F, $00 EQUB $36, $00, $7F, $03, $7F, $63, $7F, $00 EQUB $36, $00, $7F, $63, $63, $63, $7F, $00 EQUB $36, $00, $63, $63, $63, $63, $7F, $00 IF _NTSC EQUB $00, $8D, $06 ; These bytes appear to be unused EQUB $20, $A9, $4C EQUB $00, $C0, $45 EQUB $4C, $20, $20 EQUB $20, $20, $20 EQUB $20, $20, $20 EQUB $20, $20, $20 EQUB $20, $20, $20 EQUB $00, $00, $00 EQUB $00, $38, $04 EQUB $01, $07, $9C EQUB $2A ELIF _PAL EQUB $FF, $FF, $FF ; These bytes appear to be unused EQUB $FF, $FF, $4C EQUB $00, $C0, $45 EQUB $4C, $20, $20 EQUB $20, $20, $20 EQUB $20, $20, $20 EQUB $20, $20, $20 EQUB $20, $20, $20 EQUB $00, $00, $00 EQUB $00, $38, $04 EQUB $01, $07, $9C EQUB $2A ENDIF
Name: Vectors_b7 [Show more] Type: Variable Category: Utility routines Summary: Vectors at the end of ROM bank 7 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
EQUW NMI ; Vector to the NMI handler EQUW ResetMMC1_b7 ; Vector to the RESET handler EQUW IRQ ; Vector to the IRQ/BRK handler
Save bank7.bin
PRINT "S.bank7.bin ", ~CODE_BANK_7%, " ", ~P%, " ", ~LOAD_BANK_7%, " ", ~LOAD_BANK_7% SAVE "3-assembled-output/bank7.bin", CODE_BANK_7%, P%, LOAD_BANK_7%