Skip to navigation

Elite on the BBC Micro and NES

Bank 7 (Part 1 of 4)

[NES version]

NES ELITE GAME SOURCE (BANK 7) NES Elite was written by Ian Bell and David Braben and is copyright D. Braben and I. Bell 1991/1992 The code on this site has been reconstructed from a disassembly of the version released on Ian Bell's personal website at http://www.elitehomepage.org/ The commentary is copyright Mark Moxon, and any misunderstandings or mistakes in the documentation are entirely my fault The terminology and notations used in this commentary are explained at https://www.bbcelite.com/terminology The deep dive articles referred to in this commentary can be found at https://www.bbcelite.com/deep_dives
This source file produces the following binary file: * bank7.bin
ELITE BANK 7 Produces the binary file bank7.bin.
CODE_BANK_7% = $C000 ; The address where the code will be run LOAD_BANK_7% = $C000 ; The address where the code will be loaded ORG CODE_BANK_7%
Name: ResetMMC1_b7 [Show more] Type: Variable Category: Start and end Summary: The MMC1 mapper reset routine at the start of the ROM bank Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this variable on its own page References: This variable is used as follows: * Vectors_b7 uses ResetMMC1_b7

When the NES is switched on, it is hardwired to perform a JMP ($FFFC). At this point, there is no guarantee as to which ROM banks are mapped to $8000 and $C000, so to ensure that the game starts up correctly, we put the same code in each ROM at the following locations: * We put $C000 in address $FFFC in every ROM bank, so the NES always jumps to $C000 when it starts up via the JMP ($FFFC), irrespective of which ROM bank is mapped to $C000. * We put the same reset routine (this routine, ResetMMC1) at the start of every ROM bank, so the same routine gets run, whichever ROM bank is mapped to $C000. This ResetMMC1 routine is therefore called when the NES starts up, whatever bank configuration ends up being. It then switches ROM bank 7 to $C000 and jumps into bank 7 at the game's entry point BEGIN, which starts the game. We need to give a different label to this version of the reset routine so we can assemble bank 7 at the same time as banks 0 to 6, to enable the lower banks to see the exported addresses for bank 7.
.ResetMMC1_b7 SEI ; Disable interrupts INC $C006 ; Reset the MMC1 mapper, which we can do by writing a ; value with bit 7 set into any address in ROM space ; (i.e. any address from $8000 to $FFFF) ; ; The INC instruction does this in a more efficient ; manner than an LDA/STA pair, as it: ; ; * Fetches the contents of address $C006, which ; contains the high byte of the JMP destination ; below, i.e. the high byte of BEGIN, which is $C0 ; ; * Adds 1, to give $C1 ; ; * Writes the value $C1 back to address $C006 ; ; $C006 is in the ROM space and $C1 has bit 7 set, so ; the INC does all that is required to reset the mapper, ; in fewer cycles and bytes than an LDA/STA pair ; ; Resetting MMC1 maps bank 7 to $C000 and enables the ; bank at $8000 to be switched, so this instruction ; ensures that bank 7 is present JMP BEGIN ; Jump to BEGIN in bank 7 to start the game
Name: BEGIN [Show more] Type: Subroutine Category: Start and end Summary: Run through the NES initialisation process, reset the variables and start the game
Context: See this subroutine on its own page References: This subroutine is called as follows: * ResetMMC1_b0 calls BEGIN * ResetMMC1_b1 calls BEGIN * ResetMMC1_b2 calls BEGIN * ResetMMC1_b3 calls BEGIN * ResetMMC1_b4 calls BEGIN * ResetMMC1_b5 calls BEGIN * ResetMMC1_b6 calls BEGIN * ResetMMC1_b7 calls BEGIN
.BEGIN SEI ; Disable interrupts CLD ; Clear the decimal flag, so we're not in decimal mode ; (this has no effect on the NES, as BCD mode is ; disabled in the NES's CPU, but we do this to ensure ; compatibility with 6502-based debuggers) LDX #$FF ; Set the stack pointer to $01FF, which is the standard TXS ; location for the 6502 stack, so this instruction ; effectively resets the stack LDX #0 ; Set startupDebug = 0 (though this value is never read, STX startupDebug ; so this has no effect) LDA #%00010000 ; Configure the PPU by setting PPU_CTRL as follows: STA PPU_CTRL ; ; * Bits 0-1 = base nametable address %00 ($2000) ; * Bit 2 clear = increment PPU_ADDR by 1 each time ; * Bit 3 clear = sprite pattern table is at $0000 ; * Bit 4 set = background pattern table is at $1000 ; * Bit 5 clear = sprites are 8x8 pixels ; * Bit 6 clear = use PPU 0 (the only option on a NES) ; * Bit 7 clear = disable VBlank NMI generation STA ppuCtrlCopy ; Store the new value of PPU_CTRL in ppuCtrlCopy so we ; can check its value without having to access the PPU LDA #%00000000 ; Configure the PPU by setting PPU_MASK as follows: STA PPU_MASK ; ; * Bit 0 clear = normal colour (not monochrome) ; * Bit 1 clear = hide leftmost 8 pixels of background ; * Bit 2 clear = hide sprites in leftmost 8 pixels ; * Bit 3 clear = hide background ; * Bit 4 clear = hide sprites ; * Bit 5 clear = do not intensify greens ; * Bit 6 clear = do not intensify blues ; * Bit 7 clear = do not intensify reds ; We now wait for three VBlanks to pass to ensure that ; the PPU has stabilised after starting up .sper1 LDA PPU_STATUS ; Wait for the first VBlank to pass, which will set bit BPL sper1 ; 7 of PPU_STATUS (and reading PPU_STATUS clears bit 7, ; ready for the next VBlank) .sper2 LDA PPU_STATUS ; Wait for the second VBlank to pass BPL sper2 .sper3 LDA PPU_STATUS ; Wait for the third VBlank to pass BPL sper3 LDA #0 ; Set K% = 0 (English) to set as the default highlighted STA K% ; language on the Start screen (see the ChooseLanguage ; routine) LDA #60 ; Set K%+1 = 60 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 (see the ChooseLanguage ; routine) ; Fall through into ResetToStartScreen to reset memory ; and show the Start screen
Name: ResetToStartScreen [Show more] Type: Subroutine Category: Start and end Summary: Reset the stack and the game's variables and show the Start screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawTitleScreen calls ResetToStartScreen
.ResetToStartScreen LDX #$FF ; Set the stack pointer to $01FF, which is the standard TXS ; location for the 6502 stack, so this instruction ; effectively resets the stack JSR ResetVariables ; Reset all the RAM (in both the NES and cartridge), as ; it is in an undefined state when the NES is switched ; on, initialise all the game's variables, and switch to ; ROM bank 0 JMP ShowStartScreen ; Jump to ShowStartScreen in bank 0 to show the start ; screen and start the game
Name: ResetVariables [Show more] Type: Subroutine Category: Start and end Summary: Reset all the RAM (in both the NES and cartridge), initialise all the game's variables, and switch to ROM bank 0
Context: See this subroutine on its own page References: This subroutine is called as follows: * ResetToStartScreen calls ResetVariables
.ResetVariables LDA #%00000000 ; Configure the PPU by setting PPU_CTRL as follows: STA PPU_CTRL ; ; * Bits 0-1 = base nametable address %00 ($2000) ; * Bit 2 clear = increment PPU_ADDR by 1 each time ; * Bit 3 clear = sprite pattern table is at $0000 ; * Bit 4 clear = background pattern table is at $0000 ; * Bit 5 clear = sprites are 8x8 pixels ; * Bit 6 clear = use PPU 0 (the only option on a NES) ; * Bit 7 clear = disable VBlank NMI generation STA ppuCtrlCopy ; Store the new value of PPU_CTRL in ppuCtrlCopy so we ; can check its value without having to access the PPU STA PPU_MASK ; Configure the PPU by setting PPU_MASK as follows: ; ; * Bit 0 clear = normal colour (not monochrome) ; * Bit 1 clear = hide leftmost 8 pixels of background ; * Bit 2 clear = hide sprites in leftmost 8 pixels ; * Bit 3 clear = hide background ; * Bit 4 clear = hide sprites ; * Bit 5 clear = do not intensify greens ; * Bit 6 clear = do not intensify blues ; * Bit 7 clear = do not intensify reds STA setupPPUForIconBar ; Clear bit 7 of setupPPUForIconBar so we do nothing ; when the PPU starts drawing the icon bar LDA #%01000000 ; Configure the APU Frame Counter as follows: STA APU_FC ; ; * Bit 6 set = do not trigger an IRQ on the last tick ; ; * Bit 7 clear = select the four-step sequence INC $C006 ; Reset the MMC1 mapper, which we can do by writing a ; value with bit 7 set into any address in ROM space ; (i.e. any address from $8000 to $FFFF) ; ; The INC instruction does this in a more efficient ; manner than an LDA/STA pair, as it: ; ; * Fetches the contents of address $C006, which ; contains the high byte of the JMP destination ; in the JMP BEGIN instruction, i.e. the high byte ; of BEGIN, which is $C0 ; ; * Adds 1, to give $C1 ; ; * Writes the value $C1 back to address $C006 ; ; $C006 is in the ROM space and $C1 has bit 7 set, so ; the INC does all that is required to reset the mapper, ; in fewer cycles and bytes than an LDA/STA pair ; ; Resetting MMC1 maps bank 7 to $C000 and enables the ; bank at $8000 to be switched, so this instruction ; ensures that bank 7 is present LDA PPU_STATUS ; Read the PPU_STATUS register, which clears the VBlank ; latch in bit 7, so the following loops will wait for ; three VBlanks in total .resv1 LDA PPU_STATUS ; Wait for the first VBlank to pass, which will set bit BPL resv1 ; 7 of PPU_STATUS (and reading PPU_STATUS clears bit 7, ; ready for the next VBlank) .resv2 LDA PPU_STATUS ; Wait for the second VBlank to pass BPL resv2 .resv3 LDA PPU_STATUS ; Wait for the third VBlank to pass BPL resv3 ; We now zero the RAM in the NES, as follows: ; ; * Zero page from $0000 to $00FF ; ; * The rest of RAM from $0300 to $05FF ; ; This clears all of the NES's built-in RAM except for ; page 1, which is used for the stack LDA #0 ; Set A to zero so we can poke it into memory TAX ; Set X to 0 to use as an index counter as we loop ; through zero page .resv4 STA ZP,X ; Zero the X-th byte of zero page at ZP INX ; Increment the byte counter BNE resv4 ; Loop back until we have zeroed the whole of zero page ; from $0000 to $00FF LDA #$03 ; Set SC(1 0) = $0300 STA SC+1 LDA #$00 STA SC TXA ; Set A = 0 once again so we can poke it into memory LDX #3 ; We now zero three pages of memory at $0300, $0400 and ; $0500, so set a page counter in X TAY ; Set Y = 0 to use as an index counter for each page of ; memory .resv5 STA (SC),Y ; Zero the Y-th byte of the page at SC(1 0) INY ; Increment the byte counter BNE resv5 ; Loop back until we have zeroed the whole page of ; memory at SC(1 0) INC SC+1 ; Increment the high byte of SC(1 0) so it points at the ; next page of memory DEX ; Decrement the page counter BNE resv5 ; Loop back until we have zeroed three pages of memory ; from $0300 to $05FF JSR SetupMMC1 ; Configure the MMC1 mapper and page ROM bank 0 into ; memory at $8000 JSR ResetMusic ; Reset the current tune to 0 and stop the music LDA #%10000000 ; Set A = 0 and set the C flag ASL A JSR ResetScreen_b3 ; Reset the screen by clearing down the PPU, setting ; all colours to black, and resetting the screen-related ; variables JSR SetDrawingPlaneTo0 ; Set the drawing bitplane to 0 JSR ResetBuffers ; Reset the pattern and nametable buffers LDA #00000000 ; Set DTW6 = %00000000 so lower case is not enabled STA DTW6 LDA #%11111111 ; Set DTW2 = %11111111 to denote that we are not STA DTW2 ; currently printing a word LDA #%11111111 ; Set DTW8 = %11111111 to denote that we do not STA DTW8 ; capitalise the next character ; Fall through into SetBank0 to page ROM bank 0 into ; memory
Name: SetBank0 [Show more] Type: Subroutine Category: Utility routines Summary: 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: * SetupMMC1 calls SetBank0
.SetBank0 LDA #0 ; Page ROM bank 0 into memory at $8000 and return from JMP SetBank ; the subroutine using a tail call
Name: SetNonZeroBank [Show more] Type: Subroutine Category: Utility routines Summary: An unused routine that pages a specified ROM bank into memory at $8000, but only if it is non-zero 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

Arguments: A The number of the ROM bank to page into memory at $8000
.SetNonZeroBank CMP currentBank ; If the ROM bank number in A is non-zero, jump to BNE SetBank ; SetBank to page bank A into memory, returning from the ; subroutine using a tail call RTS ; Otherwise return from the subroutine
Name: ResetBank [Show more] Type: Subroutine Category: Utility routines Summary: Retrieve a ROM bank number from the stack and page that bank 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: * BEEP_b7 calls ResetBank * ChangeCmdrName_b6 calls ResetBank * ChangeToView_b0 calls ResetBank * CheckSaveSlots_b6 calls ResetBank * ChooseLanguage_b6 calls ResetBank * ChooseMusic_b6 calls ResetBank * CHPR_b2 calls ResetBank * CIRCLE2_b1 calls ResetBank * ClearDashEdge_b6 calls ResetBank * ClearScreen_b3 calls ResetBank * CLIP_b1 calls ResetBank * DASC_b2 calls ResetBank * DETOK_b2 calls ResetBank * DIALS_b6 calls ResetBank * DrawBackground_b3 calls ResetBank * DrawBigLogo_b4 calls ResetBank * DrawCmdrImage_b6 calls ResetBank * DrawDashNames_b3 calls ResetBank * DrawEquipment_b6 calls ResetBank * DrawImageFrame_b3 calls ResetBank * DrawImageNames_b4 calls ResetBank * DrawLaunchBox_b6 calls ResetBank * DrawLightning_b6 calls ResetBank * DrawScreenInNMI_b0 calls ResetBank * DrawSmallBox_b3 calls ResetBank * DrawSmallLogo_b4 calls ResetBank * DrawSpriteImage_b6 calls ResetBank * DrawSystemImage_b3 calls ResetBank * DTS_b2 calls ResetBank * ex_b2 calls ResetBank * FadeToBlack_b3 calls ResetBank * FadeToColour_b3 calls ResetBank * GetCmdrImage_b4 calls ResetBank * GetHeadshot_b4 calls ResetBank * GetHeadshotType_b4 calls ResetBank * GetSystemBack_b5 calls ResetBank * GetSystemImage_b5 calls ResetBank * HALL_b1 calls ResetBank * HideFromScanner_b1 calls ResetBank * InputName_b6 calls ResetBank * JAMESON_b6 calls ResetBank * LL164_b6 calls ResetBank * LL9_b1 calls ResetBank * LoadHighFont_b3 calls ResetBank * LoadNormalFont_b3 calls ResetBank * MakeSounds_b6 calls ResetBank * MVS5_b0 calls ResetBank * PAS1_b0 calls ResetBank * PauseGame_b6 calls ResetBank * PDESC_b2 calls ResetBank * PrintCtrlCode_b0 calls ResetBank * ResetCommander_b6 calls ResetBank * ResetScanner_b3 calls ResetBank * ResetScreen_b3 calls ResetBank * SCAN_b1 calls ResetBank * SendBitplaneToPPU_b3 calls ResetBank * SendViewToPPU_b3 calls ResetBank * SetBank calls ResetBank * SetDemoAutoPlay_b5 calls ResetBank * SetKeyLogger_b6 calls ResetBank * SetLinePatterns_b3 calls ResetBank * SetupAfterLoad_b0 calls ResetBank * SetupIconBar_b3 calls ResetBank * SetupViewInNMI_b3 calls ResetBank * SetViewAttrs_b3 calls ResetBank * ShowIconBar_b3 calls ResetBank * ShowScrollText_b6 calls ResetBank * SIGHT_b3 calls ResetBank * STARS_b1 calls ResetBank * StartEffect_b6 calls ResetBank * StopSounds_b6 calls ResetBank * SUN_b1 calls ResetBank * SVE_b6 calls ResetBank * TIDY_b1 calls ResetBank * TT24_b6 calls ResetBank * TT27_b2 calls ResetBank * TT66_b0 calls ResetBank * UpdateIconBar_b3 calls ResetBank * UpdateView_b0 calls ResetBank

Arguments: Stack The number of the ROM bank to page into memory at $8000
.ResetBank PLA ; Retrieve the ROM bank number from the stack into A ; Fall through into SetBank to page ROM bank A into ; memory at $8000
Name: SetBank [Show more] Type: Subroutine Category: Utility routines Summary: Page a specified ROM bank 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: * BEEP_b7 calls SetBank * ChangeCmdrName_b6 calls SetBank * ChangeToView_b0 calls SetBank * CheckForPause_b0 calls SetBank * CheckSaveSlots_b6 calls SetBank * ChooseLanguage_b6 calls SetBank * ChooseMusic_b6 calls SetBank * CHPR_b2 calls SetBank * CIRCLE2_b1 calls SetBank * ClearDashEdge_b6 calls SetBank * ClearScreen_b3 calls SetBank * CLIP_b1 calls SetBank * DASC_b2 calls SetBank * DEATH2_b0 calls SetBank * DETOK_b2 calls SetBank * DIALS_b6 calls SetBank * DrawBackground_b3 calls SetBank * DrawBigLogo_b4 calls SetBank * DrawCmdrImage_b6 calls SetBank * DrawDashNames_b3 calls SetBank * DrawEquipment_b6 calls SetBank * DrawImageFrame_b3 calls SetBank * DrawImageNames_b4 calls SetBank * DrawLaunchBox_b6 calls SetBank * DrawLightning_b6 calls SetBank * DrawScreenInNMI_b0 calls SetBank * DrawSmallBox_b3 calls SetBank * DrawSmallLogo_b4 calls SetBank * DrawSpriteImage_b6 calls SetBank * DrawSystemImage_b3 calls SetBank * DTS_b2 calls SetBank * ex_b2 calls SetBank * FadeToBlack_b3 calls SetBank * FadeToColour_b3 calls SetBank * GetCmdrImage_b4 calls SetBank * GetDefaultNEWB calls SetBank * GetHeadshot_b4 calls SetBank * GetHeadshotType_b4 calls SetBank * GetShipBlueprint calls SetBank * GetSystemBack_b5 calls SetBank * GetSystemImage_b5 calls SetBank * HALL_b1 calls SetBank * HideFromScanner_b1 calls SetBank * IncreaseTally calls SetBank * InputName_b6 calls SetBank * JAMESON_b6 calls SetBank * LL164_b6 calls SetBank * LL9_b1 calls SetBank * LoadHighFont_b3 calls SetBank * LoadNormalFont_b3 calls SetBank * MakeSounds_b6 calls SetBank * MVS5_b0 calls SetBank * PAS1_b0 calls SetBank * PauseGame_b6 calls SetBank * PDESC_b2 calls SetBank * PlayDemo_b0 calls SetBank * PrintCtrlCode_b0 calls SetBank * ResetBankA calls SetBank * ResetBankP calls SetBank * ResetCommander_b6 calls SetBank * ResetScanner_b3 calls SetBank * ResetScreen_b3 calls SetBank * SCAN_b1 calls SetBank * SendBitplaneToPPU_b3 calls SetBank * SendViewToPPU_b3 calls SetBank * SetBank0 calls SetBank * SetDemoAutoPlay_b5 calls SetBank * SetKeyLogger_b6 calls SetBank * SetLinePatterns_b3 calls SetBank * SetNonZeroBank calls SetBank * SetupAfterLoad_b0 calls SetBank * SetupIconBar_b3 calls SetBank * SetupViewInNMI_b3 calls SetBank * SetViewAttrs_b3 calls SetBank * ShowIconBar_b3 calls SetBank * ShowScrollText_b6 calls SetBank * SIGHT_b3 calls SetBank * STARS_b1 calls SetBank * StartEffect_b6 calls SetBank * StartGame_b0 calls SetBank * StopSounds_b6 calls SetBank * SUN_b1 calls SetBank * SVE_b6 calls SetBank * TIDY_b1 calls SetBank * TT24_b6 calls SetBank * TT27_b2 calls SetBank * TT66_b0 calls SetBank * UpdateHangarView calls SetBank * UpdateIconBar_b3 calls SetBank * UpdateView_b0 calls SetBank

Arguments: A The number of the ROM bank to page into memory at $8000
.SetBank DEC runningSetBank ; Decrement runningSetBank from 0 to $FF to denote that ; we are in the process of switching ROM banks ; ; This will disable the call to MakeSounds in the NMI ; handler, which instead will increment runningSetBank ; each time it is called STA currentBank ; Store the number of the new ROM bank in currentBank STA $FFFF ; Set the MMC1 PRG bank register (which is mapped to LSR A ; $C000-$DFFF) to the ROM bank number in A, to map the STA $FFFF ; specified ROM bank into memory at $8000 LSR A ; STA $FFFF ; Bit 4 of the ROM bank number will be zero, as A is in LSR A ; the range 0 to 7, which also ensures that PRG-RAM is STA $FFFF ; enabled and mapped to $6000-$7FFF LSR A STA $FFFF INC runningSetBank ; Increment runningSetBank again BNE sban1 ; If runningSetBank is non-zero, then this means the NMI ; handler was called while we were switching the ROM ; bank, in which case MakeSounds won't have been called ; in the NMI handler, so jump to sban1 to call the ; MakeSounds routine now instead RTS ; Return from the subroutine .sban1 LDA #0 ; Set runningSetBank = 0 so the NMI handler knows we are STA runningSetBank ; no longer switching ROM banks LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack TXA ; Store X and Y on the stack PHA TYA PHA JSR MakeSounds_b6 ; Call the MakeSounds routine to make the current sounds ; (music and sound effects) PLA ; Retrieve X and Y from the stack TAY PLA TAX 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: xTitleScreen [Show more] Type: Variable Category: Start and end Summary: The text column for the title screen's title for each language Deep dive: Multi-language support in NES Elite
Context: See this variable on its own page References: This variable is used as follows: * TT66 uses xTitleScreen
.xTitleScreen EQUB 6 ; English EQUB 6 ; German EQUB 7 ; French EQUB 7 ; There is no fourth language, so this byte is ignored
Name: xSpaceView [Show more] Type: Variable Category: Flight Summary: The text column for the space view name for each language Deep dive: Multi-language support in NES Elite
Context: See this variable on its own page References: This variable is used as follows: * TT66 uses xSpaceView
.xSpaceView EQUB 11 ; English EQUB 9 ; German EQUB 13 ; French EQUB 10 ; There is no fourth language, so this byte is ignored IF _NTSC EQUB $20, $20, $20 ; These bytes appear to be unused EQUB $20, $10, $00 EQUB $C4, $ED, $5E EQUB $E5, $22, $E5 EQUB $22, $00, $00 EQUB $ED, $5E, $E5 EQUB $22, $09, $68 EQUB $00, $00, $00 EQUB $00 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, $FF, $FF EQUB $FF ENDIF
Name: log [Show more] Type: Variable Category: Maths (Arithmetic) Summary: Binary logarithm table (high byte)
Context: See this variable on its own page References: This variable is used as follows: * DVID4 uses log * FMLTU uses log * LL28 uses log * LOIN (Part 2 of 7) uses log * LOIN (Part 5 of 7) uses log

At byte n, the table contains the high byte of: $2000 * log10(n) / log10(2) = 32 * 256 * log10(n) / log10(2) where log10 is the logarithm to base 10. The change-of-base formula says that: log2(n) = log10(n) / log10(2) so byte n contains the high byte of: 32 * log2(n) * 256
.log SKIP 1 FOR I%, 1, 255 B% = INT($2000 * LOG(I%) / LOG(2) + 0.5) EQUB B% DIV 256 NEXT
Name: logL [Show more] Type: Variable Category: Maths (Arithmetic) Summary: Binary logarithm table (low byte)
Context: See this variable on its own page References: This variable is used as follows: * DVID4 uses logL * FMLTU uses logL * LL28 uses logL * LOIN (Part 2 of 7) uses logL * LOIN (Part 5 of 7) uses logL

Byte n contains the low byte of: 32 * log2(n) * 256
.logL SKIP 1 FOR I%, 1, 255 B% = INT($2000 * LOG(I%) / LOG(2) + 0.5) EQUB B% MOD 256 NEXT
Name: antilog [Show more] Type: Variable Category: Maths (Arithmetic) Summary: Binary antilogarithm table
Context: See this variable on its own page References: This variable is used as follows: * DVID4 uses antilog * FMLTU uses antilog * LL28 uses antilog * LOIN (Part 2 of 7) uses antilog * LOIN (Part 5 of 7) uses antilog

At byte n, the table contains: 2^((n / 2 + 128) / 16) / 256 which equals: 2^(n / 32 + 8) / 256
.antilog FOR I%, 0, 255 EQUB INT(2^((I% / 2 + 128) / 16) + 0.5) DIV 256 NEXT
Name: antilogODD [Show more] Type: Variable Category: Maths (Arithmetic) Summary: Binary antilogarithm table
Context: See this variable on its own page References: This variable is used as follows: * DVID4 uses antilogODD * FMLTU uses antilogODD * LL28 uses antilogODD * LOIN (Part 2 of 7) uses antilogODD * LOIN (Part 5 of 7) uses antilogODD

At byte n, the table contains: 2^((n / 2 + 128.25) / 16) / 256 which equals: 2^(n / 32 + 8.015625) / 256 = 2^(n / 32 + 8) * 2^(.015625) / 256 = (2^(n / 32 + 8) + 1) / 256
.antilogODD FOR I%, 0, 255 EQUB INT(2^((I% / 2 + 128.25) / 16) + 0.5) DIV 256 NEXT
Name: SNE [Show more] Type: Variable Category: Maths (Geometry) Summary: Sine/cosine table Deep dive: The sine, cosine and arctan tables Drawing circles Drawing ellipses
Context: See this variable on its own page References: This variable is used as follows: * FMLTU2 uses SNE * PLS22 uses SNE

This lookup table contains sine values for the first half of a circle, from 0 to 180 degrees (0 to PI radians). In terms of circle or ellipse line segments, there are 64 segments in a circle, so this contains sine values for segments 0 to 31. In terms of segments, to calculate the sine of the angle at segment x, we look up the value in SNE + x, and to calculate the cosine of the angle we look up the value in SNE + ((x + 16) mod 32). In terms of radians, to calculate the following: sin(theta) * 256 where theta is in radians, we look up the value in: SNE + (theta * 10) To calculate the following: cos(theta) * 256 where theta is in radians, look up the value in: SNE + ((theta * 10) + 16) mod 32 Theta must be between 0 and 3.1 radians, so theta * 10 is between 0 and 31.
.SNE FOR I%, 0, 31 N = ABS(SIN((I% / 64) * 2 * PI)) IF N >= 1 B% = 255 ELSE B% = INT(256 * N + 0.5) ENDIF EQUB B% NEXT
Name: ACT [Show more] Type: Variable Category: Maths (Geometry) Summary: Arctan table Deep dive: The sine, cosine and arctan tables
Context: See this variable on its own page References: This variable is used as follows: * ARCTAN uses ACT

This table contains lookup values for arctangent calculations involving angles in the range 0 to 45 degrees (or 0 to PI / 4 radians). To calculate the value of theta in the following: theta = arctan(t) where 0 <= t < 1, we look up the value in: ACT + (t * 32) The result will be an integer representing the angle in radians, where 256 represents a full circle of 360 degrees (2 * PI radians). The result of the lookup will therefore be an integer in the range 0 to 31, as this represents 0 to 45 degrees (0 to PI / 4 radians). The table does not support values of t >= 1 or t < 0 directly, so if we need to calculate the arctangent for an angle greater than 45 degrees, we can apply the following calculation to the result from the table: * For t > 1, arctan(t) = 64 - arctan(1 / t) For negative values of t where -1 < t < 0, we can apply the following calculation to the result from the table: * For t < 0, arctan(-t) = 128 - arctan(t) Finally, if t < -1, we can do the first calculation to get arctan(|t|), and the second to get arctan(-|t|).
.ACT FOR I%, 0, 31 EQUB INT((128 / PI) * ATN(I% / 32) + 0.5) NEXT
Name: XX21 [Show more] Type: Variable Category: Drawing ships Summary: Ship blueprints lookup table Deep dive: Ship blueprints
Context: See this variable on its own page References: This variable is used as follows: * DEATH uses XX21 * HAS1 uses XX21 * Main flight loop (Part 4 of 16) uses XX21 * NWSHP uses XX21 * NWSPS uses XX21
.XX21 EQUW SHIP_MISSILE ; MSL = 1 = Missile EQUW SHIP_CORIOLIS ; SST = 2 = Coriolis space station EQUW SHIP_ESCAPE_POD ; ESC = 3 = Escape pod EQUW SHIP_PLATE ; PLT = 4 = Alloy plate EQUW SHIP_CANISTER ; OIL = 5 = Cargo canister EQUW SHIP_BOULDER ; 6 = Boulder EQUW SHIP_ASTEROID ; AST = 7 = Asteroid EQUW SHIP_SPLINTER ; SPL = 8 = Splinter EQUW SHIP_SHUTTLE ; SHU = 9 = Shuttle EQUW SHIP_TRANSPORTER ; 10 = Transporter EQUW SHIP_COBRA_MK_3 ; CYL = 11 = Cobra Mk III EQUW SHIP_PYTHON ; 12 = Python EQUW SHIP_BOA ; 13 = Boa EQUW SHIP_ANACONDA ; ANA = 14 = Anaconda EQUW SHIP_ROCK_HERMIT ; HER = 15 = Rock hermit (asteroid) EQUW SHIP_VIPER ; COPS = 16 = Viper EQUW SHIP_SIDEWINDER ; SH3 = 17 = Sidewinder EQUW SHIP_MAMBA ; 18 = Mamba EQUW SHIP_KRAIT ; KRA = 19 = Krait EQUW SHIP_ADDER ; ADA = 20 = Adder EQUW SHIP_GECKO ; 21 = Gecko EQUW SHIP_COBRA_MK_1 ; 22 = Cobra Mk I EQUW SHIP_WORM ; WRM = 23 = Worm EQUW SHIP_COBRA_MK_3_P ; CYL2 = 24 = Cobra Mk III (pirate) EQUW SHIP_ASP_MK_2 ; ASP = 25 = Asp Mk II EQUW SHIP_PYTHON_P ; 26 = Python (pirate) EQUW SHIP_FER_DE_LANCE ; 27 = Fer-de-lance EQUW SHIP_MORAY ; 28 = Moray EQUW SHIP_THARGOID ; THG = 29 = Thargoid EQUW SHIP_THARGON ; TGL = 30 = Thargon EQUW SHIP_CONSTRICTOR ; CON = 31 = Constrictor EQUW SHIP_COUGAR ; COU = 32 = Cougar EQUW SHIP_DODO ; DOD = 33 = Dodecahedron ("Dodo") space station
Name: SendBarNamesToPPU [Show more] Type: Subroutine Category: PPU Summary: Send the nametable entries for the icon bar to the PPU Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendBarNamesToPPUS calls SendBarNamesToPPU

Nametable data for the icon bar is sent to PPU nametables 0 and 1.
.SendBarNamesToPPU SUBTRACT_CYCLES 2131 ; Subtract 2131 from the cycle count LDX iconBarRow ; Set X to the low byte of iconBarRow(1 0), to use in ; the following calculations STX dataForPPU ; Set dataForPPU(1 0) = nameBuffer0 + iconBarRow(1 0) LDA iconBarRow+1 ; CLC ; So dataForPPU(1 0) points to the entry in nametable ADC #HI(nameBuffer0) ; buffer 0 for the start of the icon bar (the addition STA dataForPPU+1 ; works because the low byte of nameBuffer0 is 0) LDA iconBarRow+1 ; Set (A X) = PPU_NAME_0 + iconBarRow(1 0) ADC #HI(PPU_NAME_0) ; ; The addition works because the low byte of PPU_NAME_0 ; is 0 STA PPU_ADDR ; Set PPU_ADDR = (A X) STX PPU_ADDR ; = PPU_NAME_0 + iconBarRow(1 0) ; ; So PPU_ADDR points to the tile entry in the PPU's ; nametable 0 for the start of the icon bar LDY #0 ; We now send the nametable entries for the icon bar to ; the PPU's nametable 0, so set a counter in Y .ibar1 LDA (dataForPPU),Y ; Send the Y-th nametable entry from dataForPPU(1 0) to STA PPU_DATA ; the PPU INY ; Increment the loop counter CPY #2*32 ; Loop back until we have sent 2 rows of 32 tiles BNE ibar1 LDA iconBarRow+1 ; Set (A X) = PPU_NAME_1 + iconBarRow(1 0) ADC #HI(PPU_NAME_1-1) ; ; The addition works because the low byte of PPU_NAME_1 ; is 0 and because the C flag is set (as we just passed ; through the BNE above) STA PPU_ADDR ; Set PPU_ADDR = (A X) STX PPU_ADDR ; = PPU_NAME_1 + iconBarRow(1 0) ; ; So PPU_ADDR points to the tile entry in the PPU's ; nametable 1 for the start of the icon bar LDY #0 ; We now send the nametable entries for the icon bar to ; the PPU's nametable 1, so set a counter in Y .ibar2 LDA (dataForPPU),Y ; Send the Y-th nametable entry from dataForPPU(1 0) to STA PPU_DATA ; the PPU INY ; Increment the loop counter CPY #2*32 ; Loop back until we have sent 2 rows of 32 tiles BNE ibar2 LDA skipBarPatternsPPU ; If bit 7 of skipBarPatternsPPU is set, we do not send BMI ibar3 ; the pattern data to the PPU, so jump to ibar3 to skip ; the following JMP SendBarPattsToPPU ; Bit 7 of skipBarPatternsPPU is clear, we do want to ; send the icon bar's pattern data to the PPU, so jump ; to SendBarPattsToPPU to do just that, returning from ; the subroutine using a tail call .ibar3 STA barPatternCounter ; Set barPatternCounter = 128 so the NMI handler does ; not send any more icon bar data to the PPU JMP ConsiderSendTiles ; Jump to ConsiderSendTiles to start sending tiles to ; the PPU, but only if there are enough free cycles
Name: SendBarPatts2ToPPU [Show more] Type: Subroutine Category: PPU Summary: Send pattern data for tiles 64-127 for the icon bar to the PPU, split across multiple calls to the NMI handler if required Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendBarPattsToPPU calls SendBarPatts2ToPPU

Pattern data for icon bar patterns 64 to 127 is sent to PPU pattern table 0 only.
.SendBarPatts2ToPPU SUBTRACT_CYCLES 666 ; Subtract 666 from the cycle count BMI patt1 ; If the result is negative, jump to patt1 to stop ; sending patterns in this VBlank, as we have run out of ; cycles (we will pick up where we left off in the next ; VBlank) JMP patt2 ; The result is positive, so we have enough cycles to ; keep sending PPU data in this VBlank, so jump to patt2 ; to send the patterns .patt1 ADD_CYCLES 623 ; Add 623 to the cycle count JMP RTS1 ; Return from the subroutine (as RTS1 contains an RTS) .patt2 LDA #0 ; Set the low byte of dataForPPU(1 0) to 0 STA dataForPPU LDA barPatternCounter ; Set Y = (barPatternCounter mod 64) * 8 ASL A ; ASL A ; And set the C flag to the overflow bit ASL A ; TAY ; The mod 64 part comes from the fact that we shift bits ; 7 and 6 left out of A and discard them, so this is the ; same as (barPatternCounter AND %00111111) * 8 LDA #%00000001 ; Set addr = %0000001C ROL A ; STA addr ; And clear the C flag (as it gets set to bit 7 of A) ; ; So we now have the following: ; ; (addr Y) = (2 0) + (barPatternCounter mod 64) * 8 ; = $0200 + (barPatternCounter mod 64) * 8 ; = 64 * 8 + (barPatternCounter mod 64) * 8 ; = (64 + barPatternCounter mod 64) * 8 ; ; We only call this routine when this is true: ; ; 64 < barPatternCounter < 128 ; ; in which case we know that: ; ; 64 + barPatternCounter mod 64 = barPatternCounter ; ; So we if we substitute this into the above, we get: ; ; (addr Y) = (10 + 64 + barPatternCounter mod 64) * 8 ; = barPatternCounter * 8 TYA ; Set (A X) = (addr Y) + PPU_PATT_0 + $50 ADC #$50 ; = PPU_PATT_0 + $50 + barPatternCounter * 8 TAX ; ; Starting with the low bytes LDA addr ; And then the high bytes (this works because we know ADC #HI(PPU_PATT_0) ; the low byte of PPU_PATT_0 is 0) STA PPU_ADDR ; Set PPU_ADDR = (A X) STX PPU_ADDR ; = PPU_PATT_0 + $50 + barPatternCounter * 8 ; = PPU_PATT_0 + (10 + barPatternCounter) * 8 ; ; So PPU_ADDR points to a pattern in PPU pattern table ; 0, which is at address PPU_PATT_0 in the PPU ; ; So it points to pattern 10 when barPatternCounter is ; zero, and points to patterns 10 to 137 as ; barPatternCounter increments from 0 to 127 LDA iconBarImageHi ; Set dataForPPU(1 0) = (iconBarImageHi 0) + (addr 0) ADC addr ; STA dataForPPU+1 ; We know from above that: ; ; (addr Y) = $0200 + (barPatternCounter mod 64) * 8 ; = 64 * 8 + (barPatternCounter mod 64) * 8 ; = (64 + barPatternCounter mod 64) * 8 ; = barPatternCounter * 8 ; ; So this means that: ; ; dataForPPU(1 0) + Y ; = (iconBarImageHi 0) + (addr 0) + Y ; = (iconBarImageHi 0) + (addr Y) ; = (iconBarImageHi 0) + barPatternCounter * 8 ; ; We know that (iconBarImageHi 0) points to the current ; icon bar's image data aticonBarImage0, iconBarImage1, ; iconBarImage2, iconBarImage3 or iconBarImage4 ; ; So dataForPPU(1 0) + Y points to the pattern within ; the icon bar's image data that corresponds to pattern ; number barPatternCounter, so this is the data that we ; want to send to the PPU LDX #32 ; We now send 32 bytes to the PPU, which equates to four ; patterns (as each pattern contains eight bytes) ; ; We send 32 pattern bytes, starting from the Y-th byte ; of dataForPPU(1 0), which corresponds to pattern ; number barPatternCounter in dataForPPU(1 0) .patt3 LDA (dataForPPU),Y ; Send the Y-th byte from dataForPPU(1 0) to the PPU STA PPU_DATA INY ; Increment the index in Y to point to the next byte ; from dataForPPU(1 0) DEX ; Decrement the loop counter BEQ patt4 ; If the loop counter is now zero, jump to patt4 to exit ; the loop JMP patt3 ; Loop back to send the next byte .patt4 LDA barPatternCounter ; Add 4 to barPatternCounter, as we just sent four tile CLC ; patterns ADC #4 STA barPatternCounter BPL SendBarPatts2ToPPU ; If barPatternCounter < 128, loop back to the start of ; the routine to send another four patterns JMP ConsiderSendTiles ; Jump to ConsiderSendTiles to start sending tiles to ; the PPU, but only if there are enough free cycles
Name: SendBarPattsToPPU [Show more] Type: Subroutine Category: PPU Summary: Send pattern data for tiles 0-127 for the icon bar to the PPU, split across multiple calls to the NMI handler if required Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendBarNamesToPPU calls SendBarPattsToPPU * SendBarPattsToPPUS calls SendBarPattsToPPU

Pattern data for icon bar patterns 0 to 63 is sent to both pattern table 0 and 1 in the PPU, while pattern data for icon bar patterns 64 to 127 is sent to pattern table 0 only (the latter is done via the SendBarPatts2ToPPU routine).
Arguments: A A counter for the icon bar patterns to send to the PPU, which works its way from 0 to 128 as pattern data is sent to the PPU over successive calls to the NMI handler
.SendBarPattsToPPU ASL A ; If bit 6 of A is set, then 64 < A < 128, so jump to BMI SendBarPatts2ToPPU ; SendBarPatts2ToPPU to send patterns 64 to 127 to ; pattern table 0 in the PPU ; If we get here then both bit 6 and bit 7 of A are ; clear, so 0 < A < 64, so we now send patterns 0 to 63 ; to pattern table 0 and 1 in the PPU SUBTRACT_CYCLES 1297 ; Subtract 1297 from the cycle count BMI patn1 ; If the result is negative, jump to patn1 to stop ; sending patterns in this VBlank, as we have run out of ; cycles (we will pick up where we left off in the next ; VBlank) JMP patn2 ; The result is positive, so we have enough cycles to ; keep sending PPU data in this VBlank, so jump to patn2 ; to send the patterns .patn1 ADD_CYCLES 1251 ; Add 1251 to the cycle count JMP RTS1 ; Return from the subroutine (as RTS1 contains an RTS) .patn2 LDA #0 ; Set the low byte of dataForPPU(1 0) to 0 STA dataForPPU LDA barPatternCounter ; Set Y = barPatternCounter * 8 ASL A ; ASL A ; And set the C flag to the overflow bit ASL A ; TAY ; Note that in the above we shift bits 7 and 6 left out ; out of A and discard them, but because we know that ; 0 < barPatternCounter < 64, this has no effect LDA #%00000000 ; Set addr = %0000000C ROL A ; STA addr ; And clear the C flag (as it gets set to bit 7 of A) ; ; So we now have the following: ; ; (addr Y) = barPatternCounter * 8 TYA ; Set (A X) = (addr Y) + PPU_PATT_0 + $50 ADC #$50 ; = PPU_PATT_0 + $50 + barPatternCounter * 8 TAX ; ; Starting with the low bytes LDA addr ; And then the high bytes (this works because we know ADC #HI(PPU_PATT_0) ; the low byte of PPU_PATT_0 is 0) STA PPU_ADDR ; Set PPU_ADDR = (A X) STX PPU_ADDR ; = PPU_PATT_0 + $50 + barPatternCounter * 8 ; = PPU_PATT_0 + (10 + barPatternCounter) * 8 ; ; So PPU_ADDR points to a pattern in PPU pattern table ; 0, which is at address PPU_PATT_0 in the PPU ; ; So it points to pattern 10 when barPatternCounter is ; zero, and points to patterns 10 to 137 as ; barPatternCounter increments from 0 to 127 LDA iconBarImageHi ; Set dataForPPU(1 0) = (iconBarImageHi 0) + (addr 0) ADC addr ; STA dataForPPU+1 ; This means that: ; ; dataForPPU(1 0) + Y ; = (iconBarImageHi 0) + (addr 0) + Y ; = (iconBarImageHi 0) + (addr Y) ; = (iconBarImageHi 0) + barPatternCounter * 8 ; ; We know that (iconBarImageHi 0) points to the current ; icon bar's image data aticonBarImage0, iconBarImage1, ; iconBarImage2, iconBarImage3 or iconBarImage4 ; ; So dataForPPU(1 0) + Y points to the pattern within ; the icon bar's image data that corresponds to pattern ; number barPatternCounter, so this is the data that we ; want to send to the PPU LDX #32 ; We now send 32 bytes to the PPU, which equates to four ; patterns (as each pattern contains eight bytes) ; ; We send 32 pattern bytes, starting from the Y-th byte ; of dataForPPU(1 0), which corresponds to pattern ; number barPatternCounter in dataForPPU(1 0) .patn3 LDA (dataForPPU),Y ; Send the Y-th byte from dataForPPU(1 0) to the PPU STA PPU_DATA INY ; Increment the index in Y to point to the next byte ; from dataForPPU(1 0) DEX ; Decrement the loop counter BEQ patn4 ; If the loop counter is now zero, jump to patn4 to exit ; the loop JMP patn3 ; Loop back to send the next byte .patn4 LDA #0 ; Set the low byte of dataForPPU(1 0) to 0 STA dataForPPU LDA barPatternCounter ; Set Y = barPatternCounter * 8 ASL A ; ASL A ; And set the C flag to the overflow bit ASL A ; TAY ; Note that in the above we shift bits 7 and 6 left out ; out of A and discard them, but because we know that ; 0 < barPatternCounter < 64, this has no effect LDA #%00000000 ; Set addr = %0000000C ROL A ; STA addr ; And clear the C flag (as it gets set to bit 7 of A) ; ; So we now have the following: ; ; (addr Y) = barPatternCounter * 8 TYA ; Set (A X) = (addr Y) + PPU_PATT_1 + $50 ADC #$50 ; = PPU_PATT_1 + $50 + barPatternCounter * 8 TAX ; ; Starting with the low bytes LDA addr ; And then the high bytes (this works because we know ADC #HI(PPU_PATT_1) ; the low byte of PPU_PATT_1 is 0) STA PPU_ADDR ; Set PPU_ADDR = (A X) STX PPU_ADDR ; = PPU_PATT_1 + $50 + barPatternCounter * 8 ; = PPU_PATT_1 + (10 + barPatternCounter) * 8 ; ; So PPU_ADDR points to a pattern in PPU pattern table ; 1, which is at address PPU_PATT_1 in the PPU ; ; So it points to pattern 10 when barPatternCounter is ; zero, and points to patterns 10 to 137 as ; barPatternCounter increments from 0 to 127 LDA iconBarImageHi ; Set dataForPPU(1 0) = (iconBarImageHi 0) + (addr 0) ADC addr ; STA dataForPPU+1 ; This means that: ; ; dataForPPU(1 0) + Y ; = (iconBarImageHi 0) + (addr 0) + Y ; = (iconBarImageHi 0) + (addr Y) ; = (iconBarImageHi 0) + barPatternCounter * 8 ; ; We know that (iconBarImageHi 0) points to the current ; icon bar's image data aticonBarImage0, iconBarImage1, ; iconBarImage2, iconBarImage3 or iconBarImage4 ; ; So dataForPPU(1 0) + Y points to the pattern within ; the icon bar's image data that corresponds to pattern ; number barPatternCounter, so this is the data that we ; want to send to the PPU LDX #32 ; We now send 32 bytes to the PPU, which equates to four ; patterns (as each pattern contains eight bytes) ; ; We send 32 pattern bytes, starting from the Y-th byte ; of dataForPPU(1 0), which corresponds to pattern ; number barPatternCounter in dataForPPU(1 0) .patn5 LDA (dataForPPU),Y ; Send the Y-th byte from dataForPPU(1 0) to the PPU STA PPU_DATA INY ; Increment the index in Y to point to the next byte ; from dataForPPU(1 0) DEX ; Decrement the loop counter BEQ patn6 ; If the loop counter is now zero, jump to patn6 to exit ; the loop JMP patn5 ; Loop back to send the next byte .patn6 LDA barPatternCounter ; Add 4 to barPatternCounter, as we just sent four tile CLC ; patterns ADC #4 STA barPatternCounter JMP SendBarPattsToPPU ; Loop back to the start of the routine to send another ; four patterns to both PPU pattern tables
Name: SendBarPattsToPPUS [Show more] Type: Subroutine Category: PPU Summary: Send the pattern data for the icon bar to the PPU (this is a jump so we can call this routine using a branch instruction) Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendBuffersToPPU (Part 1 of 3) calls SendBarPattsToPPUS
.SendBarPattsToPPUS JMP SendBarPattsToPPU ; Jump to SendBarPattsToPPU to send the pattern data for ; the icon bar to the PPU, returning from the subroutine ; using a tail call
Name: SendBarNamesToPPUS [Show more] Type: Subroutine Category: PPU Summary: Send the nametable entries for the icon bar to the PPU (this is a jump so we can call this routine using a branch instruction) Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendBuffersToPPU (Part 1 of 3) calls SendBarNamesToPPUS
.SendBarNamesToPPUS JMP SendBarNamesToPPU ; Jump to SendBarNamesToPPU to send the nametable ; entries for the icon bar to the PPU, returning from ; the subroutine using a tail call
Name: ConsiderSendTiles [Show more] Type: Subroutine Category: PPU Summary: If there are enough free cycles, move on to the next stage of sending patterns to the PPU
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendBarNamesToPPU calls ConsiderSendTiles * SendBarPatts2ToPPU calls ConsiderSendTiles * SendBarPatts2ToPPU calls via RTS1 * SendBarPattsToPPU calls via RTS1 * SendBuffersToPPU (Part 2 of 3) calls via RTS1 * SendNametableToPPU calls via RTS1 * SendOtherBitplane calls via RTS1 * SendPatternsToPPU (Part 1 of 6) calls via RTS1 * SendPatternsToPPU (Part 6 of 6) calls via RTS1

Other entry points: RTS1 Contains an RTS
.ConsiderSendTiles LDX nmiBitplane ; Set X to the current NMI bitplane (i.e. the bitplane ; for which we are sending data to the PPU in the NMI ; handler) LDA bitplaneFlags,X ; Set A to the bitplane flags for the NMI bitplane AND #%00010000 ; If bit 4 of A is clear, then we are not currently in BEQ RTS1 ; the process of sending tile data to the PPU for this ; bitplane, so return from the subroutine (as RTS1 ; contains an RTS) SUBTRACT_CYCLES 42 ; Subtract 42 from the cycle count BMI next1 ; If the result is negative, jump to next1 to stop ; sending patterns in this VBlank, as we have run out of ; cycles (we will pick up where we left off in the next ; VBlank) JMP next2 ; The result is positive, so we have enough cycles to ; keep sending PPU data in this VBlank, so jump to ; SendPatternsToPPU via next2 to move on to the next ; stage of sending patterns to the PPU .next1 ADD_CYCLES 65521 ; Add 65521 to the cycle count (i.e. subtract 15) JMP RTS1 ; Return from the subroutine (as RTS1 contains an RTS) .next2 JMP SendPatternsToPPU ; Jump to SendPatternsToPPU to move on to the next stage ; of sending patterns to the PPU .RTS1 RTS ; Return from the subroutine
Name: SendBuffersToPPU (Part 1 of 3) [Show more] Type: Subroutine Category: PPU Summary: Send the icon bar nametable and palette data to the PPU, if it has changed, before moving on to tile data in part 2 Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendDataNowToPPU calls SendBuffersToPPU * SendScreenToPPU calls SendBuffersToPPU
.SendBuffersToPPU LDA barPatternCounter ; If barPatternCounter = 0, then we need to send the BEQ SendBarNamesToPPUS ; nametable entries for the icon bar to the PPU, so ; jump to SendBarNamesToPPU via SendBarNamesToPPUS, ; returning from the subroutine using a tail call BPL SendBarPattsToPPUS ; If 0 < barPatternCounter < 128, then we need to send ; the pattern data for the icon bar to the PPU, so ; jump to SendBarPattsToPPU via SendBarPattsToPPUS, ; returning from the subroutine using a tail call ; If we get here then barPatternCounter >= 128, so we ; do not need to send any icon bar data to the PPU ; Fall through into part 2 to look at sending tile data ; to the PPU for the rest of the screen
Name: SendBuffersToPPU (Part 2 of 3) [Show more] Type: Subroutine Category: PPU Summary: If we are already sending tile data to the PPU, pick up where we left off, otherwise jump to part 3 to check for new data to send Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
LDX nmiBitplane ; Set X to the current NMI bitplane (i.e. the bitplane ; for which we are sending data to the PPU in the NMI ; handler) LDA bitplaneFlags,X ; Set A to the bitplane flags for the NMI bitplane AND #%00010000 ; If bit 4 of A is clear, then we are not currently in BEQ sbuf7 ; the process of sending tile data to the PPU for this ; bitplane, so jump to sbuf7 in part 3 to start sending ; tile data ; If we get here then we are already in the process of ; sending tile data to the PPU, split across multiple ; calls to the NMI handler, so before we can consider ; sending data for anything else, we need to finish the ; job that we already started SUBTRACT_CYCLES 56 ; Subtract 56 from the cycle count TXA ; Set Y to the inverse of X, so Y is the opposite EOR #1 ; bitplane to the NMI bitplane TAY LDA bitplaneFlags,Y ; Set A to the bitplane flags for the opposite plane ; to the NMI bitplane AND #%10100000 ; If bitplanes are enabled then enableBitplanes = 1, so ORA enableBitplanes ; this jumps to sbuf2 if any of the following are true CMP #%10000001 ; for the opposite bitplane: BNE sbuf2 ; ; * Bitplanes are disabled ; ; * Bit 5 is set (we have already sent all the data ; to the PPU for the opposite bitplane) ; ; * Bit 7 is clear (do not send data to the PPU for ; the opposite bitplane) ; ; If any of these are true, we jump to SendPatternsToPPU ; via sbuf2 to continue sending tiles to the PPU for the ; current bitplane ; If we get here then the following are true: ; ; * Bitplanes are enabled ; ; * We have not sent all the data for the opposite ; bitplane to the PPU ; ; * The opposite bitplane is configured to be sent to ; the PPU LDA lastPattern,X ; Set A to the number of the last pattern number to send ; for this bitplane BNE sbuf1 ; If it is zero (i.e. we have no free tiles), then set LDA #255 ; A to 255, so we can use A as an upper limit .sbuf1 CMP sendingPattern,X ; If A >= sendingPattern, then the number of the last BEQ sbuf3 ; pattern to send is bigger than the number of the BCS sbuf3 ; pattern which we are currently sending pattern data ; to the PPU for this bitplane, which means there is ; still some pattern data to send before we have ; processed all the patterns, so jump to sbuf3 ; ; The BEQ appears to be superfluous here as BCS will ; catch an equality ; If we get here then we have finished sending pattern ; data to the PPU, so we now move on to the next stage ; by jumping to SendPatternsToPPU after adjusting the ; cycle count SUBTRACT_CYCLES 32 ; Subtract 32 from the cycle count .sbuf2 JMP SendPatternsToPPU ; Jump to SendPatternsToPPU to continue sending tile ; data to the PPU .sbuf3 ; If we get here then the following are true: ; ; * Bitplanes are enabled ; ; * We have not sent all the data for the opposite ; bitplane to the PPU ; ; * The opposite bitplane is configured to be sent to ; the PPU ; ; * We are in the process of sending data for the ; current bitplane to the PPU ; ; * We still have pattern data to send to the PPU for ; this bitplane LDA bitplaneFlags,X ; Set A to the bitplane flags for the NMI bitplane ASL A ; Shift A left by one place, so bit 7 becomes bit 6 of ; the original flags, and so on BPL RTS1 ; If bit 6 of the bitplane flags is clear, then this ; bitplane is only configured to send pattern data and ; not nametable data, and to stop sending the pattern ; data if the other bitplane is ready to be sent ; ; This is the case here as we only jump to sbuf3 if ; the other bitplane is configured to send data to the ; PPU, so we stop sending the pattern data for this ; bitplane by returning from the subroutine (as RTS1 ; contains an RTS) LDY lastNameTile,X ; Set Y to the number of the last tile we need to send ; for this bitplane, divided by 8 AND #%00001000 ; If bit 2 of the bitplane flags is set (as A was BEQ sbuf4 ; shifted left above), set Y = 128 to override the last LDY #128 ; tile number with 128, which means send all tiles (as ; 128 * 8 = 1024 and 1024 is the buffer size) .sbuf4 TYA ; Set A = Y - sendingNameTile SEC ; = lastNameTile - sendingNameTile SBC sendingNameTile,X ; ; So this is the number of tiles for which we still have ; to send nametable entries, as sendingNameTile is the ; number of the tile for which we are currently sending ; nametable entries to the PPU, divided by 8 CMP #48 ; If A < 48, then we have fewer than 48 * 8 = 384 BCC sbuf6 ; nametable entries to send, so jump to sbuf6 to swap ; the hidden and visible bitplanes before sending the ; next batch of tiles SUBTRACT_CYCLES 60 ; Subtract 60 from the cycle count .sbuf5 JMP SendPatternsToPPU ; Jump to SendPatternsToPPU to continue sending tile ; data to the PPU .sbuf6 LDA ppuCtrlCopy ; If ppuCtrlCopy is zero then we are not worried about BEQ sbuf5 ; keeping PPU writes within VBlank, so jump to sbuf5 to ; skip the following bitplane flip and crack on with ; sending data to the PPU SUBTRACT_CYCLES 134 ; Subtract 134 from the cycle count LDA enableBitplanes ; If bitplanes are enabled then enableBitplanes will be EOR hiddenBitplane ; 1, so this flips hiddenBitplane between 0 and 1 when STA hiddenBitplane ; bitplanes are enabled, and does nothing when they ; aren't (so it effectively swaps the hidden and visible ; bitplanes) JSR SetPaletteForView ; Send palette 0 for the current view to the PPU JMP SendPatternsToPPU ; Jump to SendPatternsToPPU to continue sending tile ; data to the PPU
Name: SendBuffersToPPU (Part 3 of 3) [Show more] Type: Subroutine Category: PPU Summary: If we need to send tile nametable and pattern data to the PPU for either bitplane, start doing just that Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.sbuf7 ; If we get here then we are not currently sending tile ; data to the PPU, so now we check which bitplane is ; configured to be sent, configure the NMI handler to ; send data for that bitplane to the PPU (over multiple ; calls to the NMI handler, if required), and we also ; hide the bitplane we are updating from the screen, so ; we don't corrupt the screen while updating it SUBTRACT_CYCLES 298 ; Subtract 298 from the cycle count LDA bitplaneFlags ; Set A to the bitplane flags for bitplane 0 AND #%10100000 ; This jumps to sbuf8 if any of the following are true CMP #%10000000 ; for bitplane 0: BNE sbuf8 ; ; * Bit 5 is set (we have already sent all the data ; to the PPU for bitplane 0) ; ; * Bit 7 is clear (do not send data to the PPU for ; bitplane 0) ; ; If any of these are true, we jump to sbuf8 to consider ; sending bitplane 1 instead ; If we get here then we have not already send all the ; data to the PPU for bitplane 0, and bitplane 0 is ; configured to be sent, so we start sending data for ; bitplane 0 to the PPU NOP ; This looks like code that has been removed NOP NOP NOP NOP LDX #0 ; Set X = 0 and jump to sbuf11 to start sending tile JMP sbuf11 ; data to the PPU for bitplane 0 .sbuf8 LDA bitplaneFlags+1 ; Set A to the bitplane flags for bitplane 1 AND #%10100000 ; This jumps to sbuf10 if both of the following are true CMP #%10000000 ; for bitplane 1: BEQ sbuf10 ; ; * Bit 5 is clear (we have not already sent all the ; data to the PPU for bitplane 1) ; ; * Bit 7 is set (send data to the PPU for bitplane 1) ; ; If both of these are true then jump to sbuf10 to start ; sending data for bitplane 1 to the PPU ; If we get here then we don't need to send either ; bitplane to the PPU, so we update the cycle count and ; return from the subroutine ADD_CYCLES_CLC 223 ; Add 223 to the cycle count RTS ; Return from the subroutine .sbuf9 ADD_CYCLES_CLC 45 ; Add 45 to the cycle count JMP SendTilesToPPU ; Jump to SendTilesToPPU to set up the variables for ; sending tile data to the PPU, and then send them .sbuf10 LDX #1 ; Set X = 1 so we start sending tile data to the PPU ; for bitplane 1 .sbuf11 ; If we get here then we are about to start sending tile ; data to the PPU for bitplane X, so we set nmiBitplane ; to X (so the NMI handler sends data to the PPU for ; that bitplane), and we also set hiddenBitplane to X, ; so that the bitplane we are updating is hidden from ; view (and the other bitplane is shown on-screen) ; ; So this is the part of the code that swaps animation ; frames when drawing the space view STX nmiBitplane ; Set the NMI bitplane to the value in X, which will ; be 0 or 1 depending on the value of the bitplane flags ; we tested above LDA enableBitplanes ; If enableBitplanes = 0 then bitplanes are not enabled BEQ sbuf9 ; (we must be on the Start screen), so jump to sbuf9 to ; update the cycle count and skip the following two ; instructions STX hiddenBitplane ; Set the hidden bitplane to be the same as the NMI ; bitplane, so the rest of the NMI handler update the ; hidden bitplane (we only want to update the hidden ; bitplane, to avoid messing up the screen) JSR SetPaletteForView ; Send palette 0 for the current view to the PPU ; Fall through into SendTilesToPPU to set up the ; variables for sending tile data to the PPU, and then ; send them
Name: SendTilesToPPU [Show more] Type: Subroutine Category: PPU Summary: Set up the variables needed to send the tile nametable and pattern data to the PPU, and then send them Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendBuffersToPPU (Part 3 of 3) calls SendTilesToPPU * SendOtherBitplane calls SendTilesToPPU

Arguments: X The current value of nmiBitplane
.SendTilesToPPU TXA ; Set nmiBitplane8 = X << 3 ASL A ; = nmiBitplane * 8 ASL A ; ASL A ; So nmiBitplane has the following values: STA nmiBitplane8 ; ; * 0 when nmiBitplane is 0 ; ; * 8 when nmiBitplane is 1 LSR A ; Set A = nmiBitplane << 2 ; ; So A has the following values: ; ; * 0 when nmiBitplane is 0 ; ; * 4 when nmiBitplane is 1 ORA #HI(PPU_NAME_0) ; Set the high byte of ppuNametableAddr(1 0) to STA ppuNametableAddr+1 ; HI(PPU_NAME_0) + A, which will be: ; ; * HI(PPU_NAME_0) when nmiBitplane is 0 ; ; * HI(PPU_NAME_0) + $04 when nmiBitplane is 1 LDA #HI(PPU_PATT_1) ; Set ppuPatternTableHi to point to the high byte of STA ppuPatternTableHi ; pattern table 1 in the PPU LDA #0 ; Zero the low byte of ppuNametableAddr(1 0), so we end STA ppuNametableAddr ; up with ppuNametableAddr(1 0) set to: ; ; * PPU_NAME_0 ($2000) when nmiBitplane = 0 ; ; * PPU_NAME_1 ($2400) when nmiBitplane = 1 ; ; So ppuNametableAddr(1 0) points to the correct PPU ; nametable address for this bitplane LDA firstNameTile ; Set sendingNameTile for this bitplane to the value of STA sendingNameTile,X ; firstNameTile, which contains the number of the first ; tile to send to the PPU nametable STA clearingNameTile,X ; Set clearingNameTile for this bitplane to the same ; value, so we start to clear tiles from the same point ; once they have been sent to the PPU nametable LDA firstPattern ; Set sendingPattern for this bitplane to the value of STA sendingPattern,X ; firstPattern, which contains the number of the first ; pattern to send to the PPU pattern table STA clearingPattern,X ; Set clearingPattern for this bitplane to the same ; value, so we start to clear patterns from the same ; point once they have been sent to the PPU pattern ; table LDA bitplaneFlags,X ; Set bit 4 in the bitplane flags to indicate that we ORA #%00010000 ; are now sending tile data to the PPU in the NMI STA bitplaneFlags,X ; handler (so we can detect this in the next VBlank if ; we have to split the process across multiple VBlanks) LDA #0 ; Set (addr A) to sendingPattern for this bitplane, STA addr ; which we just set to the number of the first pattern LDA sendingPattern,X ; to send to the PPU pattern table ASL A ; Set (addr A) = (pattBufferHiAddr 0) + (addr A) * 8 ROL addr ; = pattBufferX + sendingPattern * 8 ASL A ; ROL addr ; Starting with the low bytes ASL A ; ; In the above, pattBufferX is either pattBuffer0 or ; pattBuffer1, depending on the bitplane in X, as these ; are the values stored in the pattBufferHiAddr variable STA patternBufferLo,X ; Store the low byte in patternBufferLo for this ; bitplane LDA addr ; We now add the high bytes, storing the result in ROL A ; patternBufferHi for this bitplane ADC pattBufferHiAddr,X ; STA patternBufferHi,X ; So we now have the following for this bitplane: ; ; (patternBufferHi patternBufferLo) = ; pattBufferX + sendingPattern * 8 ; ; which points to the data for pattern sendingPattern in ; the pattern buffer for bitplane X LDA #0 ; Set (addr A) to sendingNameTile for this bitplane, STA addr ; which we just set to the number of the first tile to LDA sendingNameTile,X ; send to the PPU nametable ASL A ; Set (addr A) = (nameBufferHiAddr 0) + (addr A) * 8 ROL addr ; = nameBufferX + sendingNameTile * 8 ASL A ; ROL addr ; Starting with the low bytes ASL A ; ; In the above, nameBufferX is either nameBuffer0 or ; nameBuffer1, depending on the bitplane in X, as these ; are the values stored in the nameBufferHiAddr variable STA nameTileBuffLo,X ; Store the low byte in nameTileBuffLo for this bitplane ROL addr ; We now add the high bytes, storing the result in LDA addr ; nameTileBuffHi for this bitplane ADC nameBufferHiAddr,X ; STA nameTileBuffHi,X ; So we now have the following for this bitplane: ; ; (nameTileBuffHi nameTileBuffLo) = ; nameBufferX + sendingNameTile * 8 ; ; which points to the data for tile sendingNameTile in ; the nametable buffer for bitplane X LDA ppuNametableAddr+1 ; Set the high byte of the following calculation: SEC ; SBC nameBufferHiAddr,X ; (ppuToBuffNameHi 0) = (ppuNametableAddr+1 0) STA ppuToBuffNameHi,X ; - (nameBufferHiAddr 0) ; ; So ppuToBuffNameHi for this bitplane contains a high ; byte that we can add to a nametable buffer address to ; get the corresponding address in the PPU nametable JMP SendPatternsToPPU ; Now that we have set up all the variables needed, we ; can jump to SendPatternsToPPU to move on to the next ; stage of sending patterns to the PPU
Name: SendPatternsToPPU (Part 1 of 6) [Show more] Type: Subroutine Category: PPU Summary: Calculate how many patterns we need to send and jump to the most efficient routine for sending them Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * ConsiderSendTiles calls SendPatternsToPPU * SendBuffersToPPU (Part 2 of 3) calls SendPatternsToPPU * SendTilesToPPU calls SendPatternsToPPU
.spat1 ADD_CYCLES_CLC 4 ; Add 4 to the cycle count JMP SendNametableNow ; Jump to SendNametableNow to start sending nametable ; entries to the PPU immediately .spat2 JMP spat21 ; Jump down to part 4 to start sending pattern data ; until we run out of cycles .SendPatternsToPPU SUBTRACT_CYCLES 182 ; Subtract 182 from the cycle count BMI spat3 ; If the result is negative, jump to spat3 to stop ; sending PPU data in this VBlank, as we have run out of ; cycles (we will pick up where we left off in the next ; VBlank) JMP spat4 ; The result is positive, so we have enough cycles to ; keep sending PPU data in this VBlank, so jump to spat4 ; to start sending pattern data to the PPU .spat3 ADD_CYCLES 141 ; Add 141 to the cycle count JMP RTS1 ; Return from the subroutine (as RTS1 contains an RTS) .spat4 LDA lastPattern,X ; Set A to the number of the last pattern number to send ; for this bitplane BNE spat5 ; If it is zero (i.e. we have no free tiles), then set LDA #255 ; A to 255, so we can use A as an upper limit .spat5 STA lastToSend ; Store the result in lastToSend, as we want to stop ; sending patterns once we have reached this pattern LDA ppuNametableAddr+1 ; Set the high byte of the following calculation: SEC ; SBC nameBufferHiAddr,X ; (ppuToBuffNameHi 0) = (ppuNametableAddr+1 0) STA ppuToBuffNameHi,X ; - (nameBufferHiAddr 0) ; ; So ppuToBuffNameHi for this bitplane contains a high ; byte that we can add to a PPU nametable address to get ; the corresponding address in the nametable buffer LDY patternBufferLo,X ; Set Y to the low byte of the address of the pattern ; buffer for sendingPattern in bitplane X (i.e. the ; address of the next pattern we want to send) ; ; We can use this as an index when copying data from ; the pattern buffer, as we know the pattern buffers ; start on page boundaries, so the low byte of the ; address of the start of each buffer is zero LDA patternBufferHi,X ; Set the high byte of dataForPPU(1 0) to the high byte STA dataForPPU+1 ; of the pattern buffer for this bitplane, as we want ; to copy data from the pattern buffer to the PPU LDA sendingPattern,X ; Set A to the number of the next pattern we want to ; send from the pattern buffer for this bitplane STA patternCounter ; Store the number in patternCounter, so we can keep ; track of which pattern we are sending SEC ; Set A = A - lastToSend SBC lastToSend ; = patternCounter - lastToSend BCS spat1 ; If patternCounter >= lastToSend then we have already ; sent all the patterns (right up to the last one), so ; jump to spat1 to move on to sending the nametable ; entries LDX ppuCtrlCopy ; If ppuCtrlCopy is zero then we are not worried about BEQ spat6 ; keeping PPU writes within VBlank, so jump to spat6 to ; skip the following and crack on with sending as much ; pattern data as we can to the PPU ; The above subtraction underflowed, as it cleared the C ; flag, so the result in A is a negative number and we ; should interpret $BF in the following as a signed ; integer, -65 CMP #$BF ; If A < $BF BCC spat2 ; ; i.e. patternCounter - lastToSend < -65 ; lastToSend - patternCounter > 65 ; ; Then we have 65 or more patterns to sent to the PPU, ; so jump to part 4 (via spat2) to send them until we ; run out of cycles, without bothering to check for the ; last tile (as we have more patterns to send than we ; can fit into one VBlank) ; ; Otherwise we have 64 or fewer patterns to send, so ; fall through into part 2 to send them one pattern at a ; time, checking each one to see if it's the last one
Name: SendPatternsToPPU (Part 2 of 6) [Show more] Type: Subroutine Category: PPU Summary: Configure variables for sending data to the PPU one pattern at a time with checks Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.spat6 LDA patternCounter ; Set (addr A) = patternCounter LDX #0 STX addr STX dataForPPU ; Zero the low byte of dataForPPU(1 0) ; ; We set the high byte in part 1, so dataForPPU(1 0) now ; contains the address of the pattern buffer for this ; bitplane ASL A ; Set (addr X) = (addr A) << 4 ROL addr ; = patternCounter * 16 ASL A ROL addr ASL A ROL addr ASL A TAX LDA addr ; Set (A X) = (ppuPatternTableHi 0) + (addr X) ROL A ; = (ppuPatternTableHi 0) + patternCounter * 16 ADC ppuPatternTableHi ; ; ppuPatternTableHi contains the high byte of the ; address of the PPU pattern table to which we send ; patterns; it contains HI(PPU_PATT_1), so (A X) now ; contains the address in PPU pattern table 1 for ; pattern number patternCounter (as there are 16 bytes ; in the pattern table for each pattern) ; We now set both PPU_ADDR and addr(1 0) to the ; following: ; ; * (A X) when nmiBitplane is 0 ; ; * (A X) + 8 when nmiBitplane is 1 ; ; We add 8 in the second example to point the address to ; bitplane 1, as the PPU interleaves each pattern as ; 8 bytes of one bitplane followed by 8 bytes of the ; other bitplane, so bitplane 1's data always appears 8 ; bytes after the corresponding bitplane 0 data STA PPU_ADDR ; Set the high byte of PPU_ADDR to A STA addr+1 ; Set the high byte of addr to A TXA ; Set A = X + nmiBitplane8 ADC nmiBitplane8 ; = X + nmiBitplane * 8 ; ; So we add 8 to the low byte when we are writing to ; bit plane 1, otherwise we leave the low byte alone STA PPU_ADDR ; Set the low byte of PPU_ADDR to A STA addr ; Set the high byte of addr to A ; So PPU_ADDR and addr(1 0) both contain the PPU ; address to which we should send our pattern data for ; this bitplane JMP spat9 ; Jump into part 3 to send pattern data to the PPU
Name: SendPatternsToPPU (Part 3 of 6) [Show more] Type: Subroutine Category: PPU Summary: Send pattern data to the PPU for one pattern at a time, checking after each one to see if is the last one Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.spat7 INC dataForPPU+1 ; Increment the high byte of dataForPPU(1 0) to point to ; the start of the next page in memory SUBTRACT_CYCLES 27 ; Subtract 27 from the cycle count JMP spat13 ; Jump down to spat13 to continue sending data to the ; PPU .spat8 JMP spat17 ; Jump down to spat17 to move on to sending nametable ; entries to the PPU .spat9 ; This is the entry point for part 3 LDX patternCounter ; We will now work our way through patterns, sending ; data for each one, so set a counter in X that starts ; with the number of the next pattern to send to the PPU .spat10 SUBTRACT_CYCLES 400 ; Subtract 400 from the cycle count BMI spat11 ; If the result is negative, jump to spat11 to stop ; sending PPU data in this VBlank, as we have run out of ; cycles (we will pick up where we left off in the next ; VBlank) JMP spat12 ; The result is positive, so we have enough cycles to ; keep sending PPU data in this VBlank, so jump to ; spat12 to send pattern data to the PPU .spat11 ADD_CYCLES 359 ; Add 359 to the cycle count JMP spat30 ; Jump to part 6 to save progress for use in the next ; VBlank and return from the subroutine .spat12 ; If we get here then we send pattern data to the PPU SEND_DATA_TO_PPU 8 ; Send 8 bytes from dataForPPU to the PPU, starting at ; index Y and updating Y to point to the byte after the ; block that is sent BEQ spat7 ; If Y = 0 then the next byte is in the next page in ; memory, so jump to spat7 to point dataForPPU(1 0) at ; the start of this next page, before returning here .spat13 LDA addr ; Set the following: CLC ; ADC #16 ; PPU_ADDR = addr(1 0) + 16 STA addr ; LDA addr+1 ; addr(1 0) = addr(1 0) + 16 ADC #0 ; STA addr+1 ; So PPU_ADDR and addr(1 0) both point to the next STA PPU_ADDR ; tile's pattern in the PPU for this bitplane, as each LDA addr ; tile has 16 bytes of pattern data (8 in each bitplane) STA PPU_ADDR INX ; Increment the tile number in X CPX lastToSend ; If we have reached the last pattern, jump to spat19 BCS spat8 ; (via spat8 and spat17) to move on to sending the ; nametable entries SEND_DATA_TO_PPU 8 ; Send 8 bytes from dataForPPU to the PPU, starting at ; index Y and updating Y to point to the byte after the ; block that is sent BEQ spat16 ; If Y = 0 then the next byte is in the next page in ; memory, so jump to spat16 to point dataForPPU(1 0) at ; the start of this next page, before returning here ; with the C flag clear, ready for the next addition .spat14 LDA addr ; Set the following: ADC #16 ; STA addr ; PPU_ADDR = addr(1 0) + 16 LDA addr+1 ; ADC #0 ; addr(1 0) = addr(1 0) + 16 STA addr+1 ; STA PPU_ADDR ; The addition works because the C flag is clear, either LDA addr ; because we passed through the BCS above, or because we STA PPU_ADDR ; jumped to spat16 and back ; ; So PPU_ADDR and addr(1 0) both point to the next ; tile's pattern in the PPU for this bitplane, as each ; tile has 16 bytes of pattern data (8 in each bitplane) INX ; Increment the tile number in X CPX lastToSend ; If we have reached the last pattern, jump to spat19 BCS spat18 ; (via spat18) to move on to sending the nametable ; entries SEND_DATA_TO_PPU 8 ; Send 8 bytes from dataForPPU to the PPU, starting at ; index Y and updating Y to point to the byte after the ; block that is sent BEQ spat20 ; If Y = 0 then the next byte is in the next page in ; memory, so jump to spat20 to point dataForPPU(1 0) at ; the start of this next page, before returning here ; with the C flag clear, ready for the next addition .spat15 LDA addr ; Set the following: ADC #16 ; STA addr ; PPU_ADDR = addr(1 0) + 16 LDA addr+1 ; ADC #0 ; addr(1 0) = addr(1 0) + 16 STA addr+1 ; STA PPU_ADDR ; The addition works because the C flag is clear, either LDA addr ; because we passed through the BCS above, or because we STA PPU_ADDR ; jumped to spat20 and back ; ; So PPU_ADDR and addr(1 0) both point to the next ; tile's pattern in the PPU for this bitplane, as each ; tile has 16 bytes of pattern data (8 in each bitplane) INX ; Increment the tile number in X CPX lastToSend ; If we have reached the last pattern, jump to spat19 to BCS spat19 ; move on to sending the nametable entries JMP spat10 ; Otherwise we still have patterns to send, so jump back ; to spat10 to check the cycle count and potentially ; send the next batch .spat16 INC dataForPPU+1 ; Increment the high byte of dataForPPU(1 0) to point to ; the start of the next page in memory SUBTRACT_CYCLES 29 ; Subtract 29 from the cycle count CLC ; Clear the C flag so the addition works at spat14 JMP spat14 ; Jump up to spat14 to continue sending data to the PPU .spat17 ADD_CYCLES_CLC 224 ; Add 224 to the cycle count JMP spat19 ; Jump to spat19 to move on to sending nametable entries ; to the PPU .spat18 ADD_CYCLES_CLC 109 ; Add 109 to the cycle count .spat19 ; If we get here then we have sent the last tile's ; pattern data, so we now move on to sending the ; nametable entries to the PPU ; ; Before jumping to SendNametableToPPU, we need to store ; the following variables, so they can be picked up by ; the new routine: ; ; * (patternBufferHi patternBufferLo) ; ; * sendingPattern ; ; Incidentally, these are the same variables that we ; save when storing progress for the next VBlank, which ; makes sense STX patternCounter ; Store X in patternCounter to use below NOP ; This looks like code that has been removed LDX nmiBitplane ; Set (patternBufferHi patternBufferLo) for this STY patternBufferLo,X ; bitplane to dataForPPU(1 0) + Y (which is the address LDA dataForPPU+1 ; of the next byte of data to be sent from the pattern STA patternBufferHi,X ; buffer) LDA patternCounter ; Set sendingPattern for this bitplane to the value of STA sendingPattern,X ; X we stored above (which is the number / 8 of the next ; pattern to be sent from the pattern buffer) JMP SendNametableToPPU ; Jump to SendNametableToPPU to start sending the ; nametable to the PPU .spat20 INC dataForPPU+1 ; Increment the high byte of dataForPPU(1 0) to point to ; the start of the next page in memory SUBTRACT_CYCLES 29 ; Subtract 29 from the cycle count CLC ; Clear the C flag so the addition works at spat15 JMP spat15 ; Jump up to spat14 to continue sending data to the PPU
Name: SendPatternsToPPU (Part 4 of 6) [Show more] Type: Subroutine Category: PPU Summary: Configure variables for sending data to the PPU until we run out of cycles Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.spat21 LDA patternCounter ; Set (addr A) = patternCounter LDX #0 STX addr STX dataForPPU ; Zero the low byte of dataForPPU(1 0) ; ; We set the high byte in part 1, so dataForPPU(1 0) now ; contains the address of the pattern buffer for this ; bitplane ASL A ; Set (addr X) = (addr A) << 4 ROL addr ; = patternCounter * 16 ASL A ROL addr ASL A ROL addr ASL A TAX LDA addr ; Set (A X) = (ppuPatternTableHi 0) + (addr X) ROL A ; = (ppuPatternTableHi 0) + patternCounter * 16 ADC ppuPatternTableHi ; ; ppuPatternTableHi contains the high byte of the ; address of the PPU pattern table to which we send ; patterns; it contains HI(PPU_PATT_1), so (A X) now ; contains the address in PPU pattern table 1 for ; pattern number patternCounter (as there are 16 bytes ; in the pattern table for each pattern) ; We now set both PPU_ADDR and addr(1 0) to the ; following: ; ; * (A X) when nmiBitplane is 0 ; ; * (A X) + 8 when nmiBitplane is 1 ; ; We add 8 in the second example to point the address to ; bitplane 1, as the PPU interleaves each pattern as ; 8 bytes of one bitplane followed by 8 bytes of the ; other bitplane, so bitplane 1's data always appears 8 ; bytes after the corresponding bitplane 0 data STA PPU_ADDR ; Set the high byte of PPU_ADDR to A STA addr+1 ; Set the high byte of addr to A TXA ; Set A = X + nmiBitplane8 ADC nmiBitplane8 ; = X + nmiBitplane * 8 ; ; So we add 8 to the low byte when we are writing to ; bit plane 1, otherwise we leave the low byte alone STA PPU_ADDR ; Set the low byte of PPU_ADDR to A STA addr ; Set the high byte of addr to A ; So PPU_ADDR and addr(1 0) both contain the PPU ; address to which we should send our pattern data for ; this bitplane JMP spat23 ; Jump into part 5 to send pattern data to the PPU
Name: SendPatternsToPPU (Part 5 of 6) [Show more] Type: Subroutine Category: PPU Summary: Send pattern data to the PPU for two patterns at a time, until we run out of cycles (and without checking for the last pattern) Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.spat22 INC dataForPPU+1 ; Increment the high byte of dataForPPU(1 0) to point to ; the start of the next page in memory SUBTRACT_CYCLES 27 ; Subtract 27 from the cycle count JMP spat27 ; Jump down to spat27 to continue sending data to the ; PPU .spat23 ; This is the entry point for part 5 LDX patternCounter ; We will now work our way through patterns, sending ; data for each one, so set a counter in X that starts ; with the number of the next pattern to send to the PPU .spat24 SUBTRACT_CYCLES 266 ; Subtract 266 from the cycle count BMI spat25 ; If the result is negative, jump to spat25 to stop ; sending PPU data in this VBlank, as we have run out of ; cycles (we will pick up where we left off in the next ; VBlank) JMP spat26 ; The result is positive, so we have enough cycles to ; keep sending PPU data in this VBlank, so jump to ; spat26 to send pattern data to the PPU .spat25 ADD_CYCLES 225 ; Add 225 to the cycle count JMP spat30 ; Jump to part 6 to save progress for use in the next ; VBlank and return from the subroutine .spat26 ; If we get here then we send pattern data to the PPU SEND_DATA_TO_PPU 8 ; Send 8 bytes from dataForPPU to the PPU, starting at ; index Y and updating Y to point to the byte after the ; block that is sent BEQ spat22 ; If Y = 0 then the next byte is in the next page in ; memory, so jump to spat22 to point dataForPPU(1 0) at ; the start of this next page, before returning here .spat27 LDA addr ; Set the following: CLC ; ADC #16 ; PPU_ADDR = addr(1 0) + 16 STA addr ; LDA addr+1 ; addr(1 0) = addr(1 0) + 16 ADC #0 ; STA addr+1 ; So PPU_ADDR and addr(1 0) both point to the next STA PPU_ADDR ; tile's pattern in the PPU for this bitplane, as each LDA addr ; tile has 16 bytes of pattern data (8 in each bitplane) STA PPU_ADDR SEND_DATA_TO_PPU 8 ; Send 8 bytes from dataForPPU to the PPU, starting at ; index Y and updating Y to point to the byte after the ; block that is sent BEQ spat29 ; If Y = 0 then the next byte is in the next page in ; memory, so jump to spat29 to point dataForPPU(1 0) at ; the start of this next page, before returning here ; with the C flag clear, ready for the next addition .spat28 LDA addr ; Set the following: ADC #16 ; STA addr ; PPU_ADDR = addr(1 0) + 16 LDA addr+1 ; ADC #0 ; addr(1 0) = addr(1 0) + 16 STA addr+1 ; STA PPU_ADDR ; The addition works because the C flag is clear, either LDA addr ; because we passed through the BCS above, or because we STA PPU_ADDR ; jumped to spat29 and back ; ; So PPU_ADDR and addr(1 0) both point to the next ; tile's pattern in the PPU for this bitplane, as each ; tile has 16 bytes of pattern data (8 in each bitplane) INX ; Increment the tile number in X twice, as we just sent INX ; data for two tiles JMP spat24 ; Loop back to spat24 to check the cycle count and ; potentially send the next batch .spat29 INC dataForPPU+1 ; Increment the high byte of dataForPPU(1 0) to point to ; the start of the next page in memory SUBTRACT_CYCLES 29 ; Subtract 29 from the cycle count CLC ; Clear the C flag so the addition works at spat15 JMP spat28 ; Jump up to spat28 to continue sending data to the PPU
Name: SendPatternsToPPU (Part 6 of 6) [Show more] Type: Subroutine Category: PPU Summary: Save progress for use in the next VBlank and return from the subroutine Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.spat30 ; We now store the following variables, so they can be ; picked up when we return in the next VBlank: ; ; * (patternBufferHi patternBufferLo) ; ; * sendingPattern STX patternCounter ; Store X in patternCounter to use below LDX nmiBitplane ; Set (patternBufferHi patternBufferLo) for this STY patternBufferLo,X ; bitplane to dataForPPU(1 0) + Y (which is the address LDA dataForPPU+1 ; of the next byte of data to be sent from the pattern STA patternBufferHi,X ; buffer in the next VBlank) LDA patternCounter ; Set sendingPattern for this bitplane to the value of STA sendingPattern,X ; X we stored above (which is the number / 8 of the next ; pattern to be sent from the pattern buffer in the next ; VBlank) JMP RTS1 ; Return from the subroutine (as RTS1 contains an RTS)
Name: SendOtherBitplane [Show more] Type: Subroutine Category: PPU Summary: Check whether we should send another bitplane to the PPU Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendNametableToPPU calls SendOtherBitplane
.SendOtherBitplane LDX nmiBitplane ; Set X to the current NMI bitplane (i.e. the bitplane ; for which we have been sending data to the PPU) LDA #%00100000 ; Set the NMI bitplane flags as follows: STA bitplaneFlags,X ; ; * 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 set = we have already sent all the data ; * Bit 6 clear = only send pattern data to the PPU ; * Bit 7 clear = do not send data to the PPU ; ; Bits 0 and 1 are ignored and are always clear ; ; So this indicates that we have finished sending data ; to the PPU for this bitplane SUBTRACT_CYCLES 227 ; Subtract 227 from the cycle count BMI obit1 ; If the result is negative, jump to obit1 to stop ; sending PPU data in this VBlank, as we have run out of ; cycles (we will pick up where we left off in the next ; VBlank) JMP obit2 ; The result is positive, so we have enough cycles to ; keep sending PPU data in this VBlank, so jump to obit2 ; to check whether we should send this bitplane to the ; PPU .obit1 ADD_CYCLES 176 ; Add 176 to the cycle count JMP RTS1 ; Return from the subroutine (as RTS1 contains an RTS) .obit2 TXA ; Flip the NMI bitplane between 0 and 1, to it's the EOR #1 ; opposite bitplane to the one we just sent STA nmiBitplane CMP hiddenBitplane ; If the NMI bitplane is now different to the hidden BNE obit4 ; bitplane, jump to obit4 to update the cycle count ; and return from the subroutine, as we already sent ; the bitplane that's hidden (we only want to update ; the hidden bitplane, to avoid messing up the screen) ; If we get here then the new NMI bitplane is the same ; as the bitplane that's hidden, so we should send it ; to the PPU (this might happen if the value of ; hiddenBitplane changes while we are still sending ; data to the PPU across multiple calls to the NMI ; handler) TAX ; Set X to the newly flipped NMI bitplane LDA bitplaneFlags,X ; Set A to the bitplane flags for the newly flipped NMI ; bitplane AND #%10100000 ; This jumps to obit3 if both of the following are true CMP #%10000000 ; for bitplane 1: BEQ obit3 ; ; * Bit 5 is clear (we have not already sent all the ; data to the PPU for the bitplane) ; ; * Bit 7 is set (send data to the PPU for the ; bitplane) ; ; If both of these are true then jump to obit3 to update ; the cycle count and return from the subroutine without ; sending any more tile data to the PPU in this VBlank ; If we get here then the new bitplane is not configured ; to be sent to the PPU, so we send it now JMP SendTilesToPPU ; Jump to SendTilesToPPU to set up the variables for ; sending tile data to the PPU, and then send them .obit3 ADD_CYCLES_CLC 151 ; Add 151 to the cycle count RTS ; Return from the subroutine .obit4 ADD_CYCLES_CLC 163 ; Add 163 to the cycle count RTS ; Return from the subroutine
Name: SendNametableToPPU [Show more] Type: Subroutine Category: PPU Summary: Send the tile nametable to the PPU if there are enough cycles left in the current VBlank Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendPatternsToPPU (Part 3 of 6) calls SendNametableToPPU * SendPatternsToPPU (Part 1 of 6) calls via SendNametableNow

Other entry points: SendNametableNow Send the nametable without checking the cycle count
.snam1 ADD_CYCLES_CLC 58 ; Add 58 to the cycle count JMP RTS1 ; Return from the subroutine (as RTS1 contains an RTS) .snam2 ADD_CYCLES_CLC 53 ; Add 53 to the cycle count JMP SendOtherBitplane ; Jump to SendOtherBitplane to consider sending the ; other bitplane to the PPU, if required .SendNametableToPPU SUBTRACT_CYCLES 109 ; Subtract 109 from the cycle count BMI snam3 ; If the result is negative, jump to snam3 to stop ; sending PPU data in this VBlank, as we have run out of ; cycles (we will pick up where we left off in the next ; VBlank) JMP SendNametableNow ; The result is positive, so we have enough cycles to ; keep sending PPU data in this VBlank, so jump to ; SendNametableNow to start sending nametable data to ; the PPU .snam3 ADD_CYCLES 68 ; Add 68 to the cycle count JMP RTS1 ; Return from the subroutine (as RTS1 contains an RTS) .SendNametableNow LDX nmiBitplane ; Set X to the current NMI bitplane (i.e. the bitplane ; for which we are sending data to the PPU in the NMI ; handler) LDA bitplaneFlags,X ; Set A to the bitplane flags for the NMI bitplane ASL A ; Shift A left by one place, so bit 7 becomes bit 6 of ; the original flags, and so on BPL snam1 ; If bit 6 of the bitplane flags is clear, then this ; bitplane is only configured to send pattern data and ; not nametable data, so jump to snam1 to return from ; the subroutine LDY lastNameTile,X ; Set Y to the number of the last tile we need to send ; for this bitplane, divided by 8 AND #%00001000 ; If bit 2 of the bitplane flags is set (as A was BEQ snam4 ; shifted left above), set Y = 128 to override the last LDY #128 ; tile number with 128, which means send all tiles (as ; 128 * 8 = 1024 and 1024 is the buffer size) .snam4 STY lastToSend ; Store Y in lastToSend, as we want to stop sending ; nametable entries when we reach this tile LDA sendingNameTile,X ; Set A to the number of the next tile we want to send ; from the nametable buffer for this bitplane, divided ; by 8 (we divide by 8 because there are 1024 entries in ; each nametable, which doesn't fit into one byte, so we ; divide by 8 so the maximum counter value is 128) STA nameTileCounter ; Store the number in nameTileCounter, so we can keep ; track of which tile we are sending (so nameTileCounter ; contains the current tile number, divided by 8) SEC ; Set A = A - lastToSend SBC lastToSend ; = nameTileCounter - lastToSend BCS snam2 ; If nameTileCounter >= lastToSend then we have already ; sent all the nametable entries (right up to the last ; tile), so jump to snam2 to consider sending the other ; bitplane LDY nameTileBuffLo,X ; Set Y to the low byte of the address of the nametable ; buffer for sendingNameTile in bitplane X (i.e. the ; address of the next tile we want to send) ; ; We can use this as an index when copying data from ; the nametable buffer, as we know the nametable buffers ; start on page boundaries, so the low byte of the ; address of the start of each buffer is zero LDA nameTileBuffHi,X ; Set the high byte of dataForPPU(1 0) to the high byte STA dataForPPU+1 ; of the nametable buffer for this bitplane, as we want ; to copy data from the nametable buffer to the PPU CLC ; Set the high byte of the following calculation: ADC ppuToBuffNameHi,X ; ; (A 0) = (nameTileBuffHi 0) + (ppuToBuffNameHi 0) ; ; (ppuToBuffNameHi 0) for this bitplane contains a high ; byte that we can add to a nametable buffer address to ; get the corresponding address in the PPU nametable, so ; this sets (A 0) to the high byte of the correct PPU ; nametable address for this tile ; ; We already set Y as the low byte above, so we now have ; the full PPU address in (A Y) STA PPU_ADDR ; Set PPU_ADDR = (A Y) STY PPU_ADDR ; ; So PPU_ADDR points to the address in the PPU to which ; we send the nametable data LDA #0 ; Set the low byte of dataForPPU(1 0) to 0, so that STA dataForPPU ; dataForPPU(1 0) points to the start of the nametable ; buffer, and dataForPPU(1 0) + Y therefore points to ; the nametable entry for tile sendingNameTile .snam5 SUBTRACT_CYCLES 393 ; Subtract 393 from the cycle count BMI snam6 ; If the result is negative, jump to snam6 to stop ; sending PPU data in this VBlank, as we have run out of ; cycles (we will pick up where we left off in the next ; VBlank) ; If we get here then the result is positive, so the C ; flag will be set as the subtraction didn't underflow JMP snam7 ; The result is positive, so we have enough cycles to ; keep sending PPU data in this VBlank, so jump to snam7 ; to do just that .snam6 ADD_CYCLES 349 ; Add 349 to the cycle count JMP snam10 ; Jump to snam10 to save progress for use in the next ; VBlank and return from the subroutine .snam7 SEND_DATA_TO_PPU 32 ; Send 32 bytes from dataForPPU to the PPU, starting at ; index Y and updating Y to point to the byte after the ; block that is sent BEQ snam9 ; If Y = 0 then the next byte is in the next page in ; memory, so jump to snam9 to point dataForPPU(1 0) at ; the start of this next page, before looping back to ; snam5 to potentially send the next batch ; We got here by jumping to snam7 from above, which we ; did with the C flag set, so the ADC #3 below actually ; adds 4 LDA nameTileCounter ; Add 4 to nameTileCounter, as we just sent 4 * 8 = 32 ADC #3 ; nametable entries (and nameTileCounter counts the tile STA nameTileCounter ; number, divided by 8) CMP lastToSend ; If nameTileCounter >= lastToSend then we have reached BCS snam8 ; the last tile, so jump to snam8 to update the ; variables and jump to SendOtherBitplane to consider ; sending the other bitplane JMP snam5 ; Otherwise we still have nametable entries to send, so ; loop back to snam5 to check the cycles and send the ; next batch .snam8 ; If we get here then we have sent the last nametable ; entry, so we now move on to considering whether to ; send the other bitplane to the PPU, if required ; ; Before jumping to SendOtherBitplane, we need to store ; the following variables, so they can be picked up by ; the new routine: ; ; * (nameTileBuffHi nameTileBuffLo) ; ; * sendingNameTile ; ; Incidentally, these are the same variables that we ; save when storing progress for the next VBlank, which ; makes sense STA sendingNameTile,X ; Set sendingNameTile for this bitplane to the value of ; nameTileCounter, which we stored in A before jumping ; here STY nameTileBuffLo,X ; Set (nameTileBuffHi nameTileBuffLo) for this bitplane LDA dataForPPU+1 ; to dataForPPU(1 0) + Y (which is the address of the STA nameTileBuffHi,X ; next byte of data to be sent from the nametable ; buffer) JMP SendOtherBitplane ; Jump to SendOtherBitplane to consider sending the ; other bitplane to the PPU, if required .snam9 INC dataForPPU+1 ; Increment the high byte of dataForPPU(1 0) to point to ; the start of the next page in memory SUBTRACT_CYCLES 26 ; Subtract 26 from the cycle count LDA nameTileCounter ; Add 4 to nameTileCounter, as we just sent 4 * 8 = 32 CLC ; nametable entries (and nameTileCounter counts the tile ADC #4 ; number, divided by 8) STA nameTileCounter CMP lastToSend ; If nameTileCounter >= lastToSend then we have reached BCS snam8 ; the last tile, so jump to snam8 to update the ; variables and jump to SendOtherBitplane to consider ; sending the other bitplane JMP snam5 ; Otherwise we still have nametable entries to send, so ; loop back to snam5 to check the cycles and send the ; next batch .snam10 ; We now store the following variables, so they can be ; picked up when we return in the next VBlank: ; ; * (nameTileBuffHi nameTileBuffLo) ; ; * sendingNameTile LDA nameTileCounter ; Set sendingNameTile for this bitplane to the number STA sendingNameTile,X ; of the tile to send next, in nameTileCounter STY nameTileBuffLo,X ; Set (nameTileBuffHi nameTileBuffLo) for this bitplane LDA dataForPPU+1 ; to dataForPPU(1 0) + Y (which is the address of the STA nameTileBuffHi,X ; next byte of data to be sent from the nametable ; buffer) JMP RTS1 ; Return from the subroutine (as RTS1 contains an RTS)
Name: CopyNameBuffer0To1 [Show more] Type: Subroutine Category: Drawing the screen Summary: Copy the contents of nametable buffer 0 to nametable buffer 1
.CopyNameBuffer0To1 LDY #0 ; Set Y = 0 so we can use it as an index starting at 0, ; and then counting down from 255 to 0 LDX #16 ; The following loop also updates a counter in X that ; counts down from 16 to 1 and back to 16 again, but it ; isn't used anywhere, so presumably this is left over ; from some functionality that was later removed .copy1 LDA nameBuffer0,Y ; Copy the Y-th byte of nametable buffer 0 to nametable STA nameBuffer1,Y ; buffer 1, so this copies the first 256 bytes as Y ; counts down LDA nameBuffer0+256,Y ; Copy byte 256, and bytes 511 to 255 into nametable STA nameBuffer1+256,Y ; buffer 1 as Y counts down LDA nameBuffer0+512,Y ; Copy byte 512, and bytes 767 to 511 into nametable STA nameBuffer1+512,Y ; buffer 1 as Y counts down LDA nameBuffer0+768,Y ; Copy byte 768, and bytes 1023 to 769 into nametable STA nameBuffer1+768,Y ; buffer 1 as Y counts down JSR SetupPPUForIconBar ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 DEX ; Decrement the counter in X, wrapping it back up to 16 BNE copy2 ; when it reaches 0 LDX #16 .copy2 DEY ; Decrement the index counter in Y BNE copy1 ; Loop back to copy1 to copy the next four bytes, until ; we have copied the whole buffer LDA firstFreePattern ; Tell the NMI handler to send pattern entries up to the STA lastPattern ; first free pattern, for both bitplanes STA lastPattern+1 RTS ; Return from the subroutine
Name: DrawBoxTop [Show more] Type: Subroutine Category: Drawing the screen Summary: Draw the top edge of the box along the top of the screen in nametable buffer 0
Context: See this subroutine on its own page References: This subroutine is called as follows: * ClearDrawingPlane (Part 1 of 3) calls DrawBoxTop * TT66 calls DrawBoxTop
.DrawBoxTop LDY #1 ; Set Y as an index into the nametable, as we want to ; draw the top bar from column 1 to 31 LDA #3 ; Set A = 3 as the tile number to use for the top of the ; box (it's a three-pixel high horizontal bar) .boxt1 STA nameBuffer0,Y ; Set the Y-th entry in nametable 0 to tile 3 INY ; Increment the column counter CPY #32 ; Loop back until we have drawn in columns 1 through 31 BNE boxt1 RTS ; Return from the subroutine
Name: DrawBoxEdges [Show more] Type: Subroutine Category: Drawing the screen Summary: Draw the left and right edges of the box along the sides of the screen, drawing into the nametable buffer for the drawing bitplane
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawScreenInNMI calls DrawBoxEdges * SetDrawPlaneFlags calls DrawBoxEdges * UpdateView calls DrawBoxEdges
.DrawBoxEdges LDX drawingBitplane ; If the drawing bitplane is set to 1, jump to boxe1 to BNE boxe1 ; draw the box edges in bitplane 1 ; Otherwise we draw the box edges in bitplane 0 LDA boxEdge1 ; Set A to the tile number for the left edge of the box, ; which will either be tile 1 for the normal view (a ; three-pixel wide vertical bar along the right edge of ; the tile), or tile 0 (blank) for the death screen STA nameBuffer0+1 ; Write this tile into column 1 on rows 0 to 19 in STA nameBuffer0+1*32+1 ; nametable buffer 0 to draw the left edge of the box STA nameBuffer0+2*32+1 ; (column 1 is the left edge because the screen is STA nameBuffer0+3*32+1 ; scrolled horizontally by one block) STA nameBuffer0+4*32+1 STA nameBuffer0+5*32+1 STA nameBuffer0+6*32+1 STA nameBuffer0+7*32+1 STA nameBuffer0+8*32+1 STA nameBuffer0+9*32+1 STA nameBuffer0+10*32+1 STA nameBuffer0+11*32+1 STA nameBuffer0+12*32+1 STA nameBuffer0+13*32+1 STA nameBuffer0+14*32+1 STA nameBuffer0+15*32+1 STA nameBuffer0+16*32+1 STA nameBuffer0+17*32+1 STA nameBuffer0+18*32+1 STA nameBuffer0+19*32+1 LDA boxEdge2 ; Set A to the tile number for the right edge of the ; box, which will either be tile 2 for the normal view ; (a three-pixel wide vertical bar along the left edge ; of the tile), or tile 0 (blank) for the death screen STA nameBuffer0 ; Write this tile into column 0 on rows 0 to 19 in STA nameBuffer0+1*32 ; nametable buffer 0 to draw the right edge of the box STA nameBuffer0+2*32 ; (column 0 is the right edge because the screen is STA nameBuffer0+3*32 ; scrolled horizontally by one block) STA nameBuffer0+4*32 STA nameBuffer0+5*32 STA nameBuffer0+6*32 STA nameBuffer0+7*32 STA nameBuffer0+8*32 STA nameBuffer0+9*32 STA nameBuffer0+10*32 STA nameBuffer0+11*32 STA nameBuffer0+12*32 STA nameBuffer0+13*32 STA nameBuffer0+14*32 STA nameBuffer0+15*32 STA nameBuffer0+16*32 STA nameBuffer0+17*32 STA nameBuffer0+18*32 STA nameBuffer0+19*32 RTS ; Return from the subroutine .boxe1 LDA boxEdge1 ; Set A to the tile number for the left edge of the box, ; which will either be tile 1 for the normal view (a ; three-pixel wide vertical bar along the right edge of ; the tile), or tile 0 (blank) for the death screen STA nameBuffer1+1 ; Write this tile into column 1 on rows 0 to 19 in STA nameBuffer1+1*32+1 ; nametable buffer 1 to draw the left edge of the box STA nameBuffer1+2*32+1 ; (column 1 is the left edge because the screen is STA nameBuffer1+3*32+1 ; scrolled horizontally by one block) STA nameBuffer1+4*32+1 STA nameBuffer1+5*32+1 STA nameBuffer1+6*32+1 STA nameBuffer1+7*32+1 STA nameBuffer1+8*32+1 STA nameBuffer1+9*32+1 STA nameBuffer1+10*32+1 STA nameBuffer1+11*32+1 STA nameBuffer1+12*32+1 STA nameBuffer1+13*32+1 STA nameBuffer1+14*32+1 STA nameBuffer1+15*32+1 STA nameBuffer1+16*32+1 STA nameBuffer1+17*32+1 STA nameBuffer1+18*32+1 STA nameBuffer1+19*32+1 LDA boxEdge2 ; Set A to the tile number for the right edge of the ; box, which will either be tile 2 for the normal view ; (a three-pixel wide vertical bar along the left edge ; of the tile), or tile 0 (blank) for the death screen STA nameBuffer1 ; Write this tile into column 0 on rows 0 to 19 in STA nameBuffer1+1*32 ; nametable buffer 1 to draw the right edge of the box STA nameBuffer1+2*32 ; (column 0 is the right edge because the screen is STA nameBuffer1+3*32 ; scrolled horizontally by one block) STA nameBuffer1+4*32 STA nameBuffer1+5*32 STA nameBuffer1+6*32 STA nameBuffer1+7*32 STA nameBuffer1+8*32 STA nameBuffer1+9*32 STA nameBuffer1+10*32 STA nameBuffer1+11*32 STA nameBuffer1+12*32 STA nameBuffer1+13*32 STA nameBuffer1+14*32 STA nameBuffer1+15*32 STA nameBuffer1+16*32 STA nameBuffer1+17*32 STA nameBuffer1+18*32 STA nameBuffer1+19*32 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 RTS ; Return from the subroutine
Name: UNIV [Show more] Type: Variable Category: Universe Summary: Table of pointers to the local universe's ship data blocks Deep dive: The local bubble of universe
Context: See this variable on its own page References: This variable is used as follows: * GINF uses UNIV * KILLSHP uses UNIV * KS2 uses UNIV * TACTICS (Part 1 of 7) uses UNIV

See the deep dive on "Ship data blocks" for details on ship data blocks, and the deep dive on "The local bubble of universe" for details of how Elite stores the local universe in K%, FRIN and UNIV. Note that in the NES version, there are four extra bytes at the end of each K% block that don't form part of the core ship block, so each ship in K% contains NIK% = NI% + 4 bytes, rather than NI%.
.UNIV FOR I%, 0, NOSH EQUW K% + I% * NIK% ; Address of block no. I%, of size NIK%, in workspace K% NEXT
Name: GINF [Show more] Type: Subroutine Category: Universe Summary: Fetch the address of a ship's data block into INF
Context: See this subroutine on its own page References: This subroutine is called as follows: * ClearScanner calls GINF * FRMIS calls GINF * InSystemJump calls GINF * Main flight loop (Part 4 of 16) calls GINF * NWSHP calls GINF * WPSHPS calls GINF

Get the address of the data block for ship slot X and store it in INF. This address is fetched from the UNIV table, which stores the addresses of the 13 ship data blocks in workspace K%.
Arguments: X The ship slot number for which we want the data block address
.GINF TXA ; Set Y = X * 2 ASL A TAY LDA UNIV,Y ; Get the high byte of the address of the X-th ship STA INF ; from UNIV and store it in INF LDA UNIV+1,Y ; Get the low byte of the address of the X-th ship STA INF+1 ; from UNIV and store it in INF RTS ; Return from the subroutine
Name: HideExplosionBurst [Show more] Type: Subroutine Category: Drawing ships Summary: Hide the four sprites that make up the explosion burst that flashes up when a ship explodes
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOEXP calls HideExplosionBurst * LL164 calls HideExplosionBurst * RES2 calls HideExplosionBurst
.HideExplosionBurst LDX #4 ; Set X = 4 so we hide four sprites LDY #236 ; Set Y so we start hiding from sprite 236 / 4 = 59 JMP HideSprites ; Jump to HideSprites to hide four sprites from sprite ; 59 onwards (i.e. 59 to 62), returning from the ; subroutine using a tail call
Name: ClearScanner [Show more] Type: Subroutine Category: Dashboard Summary: Remove all ships from the scanner and hide the scanner sprites
Context: See this subroutine on its own page References: This subroutine is called as follows: * RES2 calls ClearScanner * ShowScrollText calls ClearScanner * TT18 calls ClearScanner * TT66 calls ClearScanner
.ClearScanner LDX #0 ; Set up a counter in X to work our way through all the ; ship slots in FRIN .csca1 LDA FRIN,X ; Fetch the ship type in slot X BEQ csca3 ; If the slot contains 0 then it is empty and we have ; checked all the slots (as they are always shuffled ; down in the main loop to close up and gaps), so jump ; to csca3WS2 as we are done BMI csca2 ; If the slot contains a ship type with bit 7 set, then ; it contains the planet or the sun, so jump down to ; csca2 to skip this slot, as the planet and sun don't ; appear on the scanner JSR GINF ; Call GINF to get the address of the data block for ; ship slot X and store it in INF LDY #31 ; Clear bit 4 in the ship's byte #31, which hides it LDA (INF),Y ; from the scanner AND #%11101111 STA (INF),Y .csca2 INX ; Increment X to point to the next ship slot BNE csca1 ; Loop back up to process the next slot (this BNE is ; effectively a JMP as X will never be zero) .csca3 LDY #44 ; Set Y so we start hiding from sprite 44 / 4 = 11 LDX #27 ; Set X = 27 so we hide 27 sprites ; Fall through into HideSprites to hide 27 sprites ; from sprite 11 onwards (i.e. the scanner sprites from ; 11 to 37)
Name: HideSprites [Show more] Type: Subroutine Category: Drawing sprites Summary: Hide X sprites from sprite Y / 4 onwards
Context: See this subroutine on its own page References: This subroutine is called as follows: * HideExplosionBurst calls HideSprites

Arguments: X The number of sprites to hide Y The number of the first sprite to hide * 4
.HideSprites LDA #240 ; Set A to the y-coordinate that's just below the bottom ; of the screen, so we can hide the required sprites by ; moving them off-screen .hspr1 STA ySprite0,Y ; Set the y-coordinate for sprite Y / 4 to 240 to hide ; it (the division by four is because each sprite in the ; sprite buffer has four bytes of data) INY ; Add 4 to Y so it points to the next sprite's data in INY ; the sprite buffer INY INY DEX ; Decrement the loop counter in X BNE hspr1 ; Loop back until we have hidden X sprites RTS ; Return from the subroutine EQUB $0C, $20, $1F ; These bytes appear to be unused
Name: nameBufferHiAddr [Show more] Type: Variable Category: Drawing the screen Summary: The high bytes of the addresses of the two nametable buffers
.nameBufferHiAddr EQUB HI(nameBuffer0) EQUB HI(nameBuffer1)
Name: pattBufferHiAddr [Show more] Type: Variable Category: Drawing the screen Summary: The high bytes of the addresses of the two pattern buffers
.pattBufferHiAddr EQUB HI(pattBuffer0) EQUB HI(pattBuffer1)
Name: IRQ [Show more] Type: Subroutine Category: Utility routines Summary: Handle IRQ interrupts by doing nothing
Context: See this subroutine on its own page References: This subroutine is called as follows: * Vectors_b0 calls IRQ * Vectors_b1 calls IRQ * Vectors_b2 calls IRQ * Vectors_b3 calls IRQ * Vectors_b4 calls IRQ * Vectors_b5 calls IRQ * Vectors_b6 calls IRQ * Vectors_b7 calls IRQ
.IRQ RTI ; Return from the interrupt handler
Name: NMI [Show more] Type: Subroutine Category: Utility routines Summary: The NMI interrupt handler that gets called every VBlank and which updates the screen, reads the controllers and plays music Deep dive: The split-screen mode in NES Elite Drawing vector graphics using NES tiles Auto-playing the combat demo
Context: See this subroutine on its own page References: This subroutine is called as follows: * Vectors_b0 calls NMI * Vectors_b1 calls NMI * Vectors_b2 calls NMI * Vectors_b3 calls NMI * Vectors_b4 calls NMI * Vectors_b5 calls NMI * Vectors_b6 calls NMI * Vectors_b7 calls NMI
.NMI JSR SendPaletteSprites ; Send the current palette and sprite data to the PPU LDA showUserInterface ; Set the value of setupPPUForIconBar so that if there STA setupPPUForIconBar ; is an on-screen user interface (which there will be if ; this isn't the game over screen), then the calls to ; the SETUP_PPU_FOR_ICON_BAR macro sprinkled throughout ; the codebase will make sure we set nametable 0 and ; palette table 0 when the PPU starts drawing the icon ; bar IF _NTSC LDA #HI(6797) ; Set cycleCount = 6797 STA cycleCount+1 ; LDA #LO(6797) ; We use this to keep track of how many cycles we have STA cycleCount ; left in the current VBlank, so we only send data to ; the PPU when VBlank is in progress, splitting up the ; larger PPU operations across multiple VBlanks ELIF _PAL LDA #HI(7433) ; Set cycleCount = 7433 STA cycleCount+1 ; LDA #LO(7433) ; We use this to keep track of how many cycles we have STA cycleCount ; left in the current VBlank, so we only send data to ; the PPU when VBlank is in progress, splitting up the ; larger PPU operations across multiple VBlanks ENDIF JSR SendScreenToPPU ; Update the screen by sending the nametable and pattern ; data from the buffers to the PPU, configuring the PPU ; registers accordingly, and clearing the buffers if ; required JSR ReadControllers ; Read the buttons on the controllers and update the ; control variables LDA autoPlayDemo ; If bit 7 of autoPlayDemo is clear then the demo is not BPL inmi1 ; being played automatically, so jump to inmi1 to skip ; the following JSR AutoPlayDemo ; Bit 7 of autoPlayDemo is set, so call AutoPlayDemo to ; automatically play the demo using the controller key ; presses in the autoPlayKeys tables .inmi1 JSR MoveIconBarPointer ; Move the sprites that make up the icon bar pointer and ; record any choices JSR UpdateJoystick ; Update the values of JSTX and JSTY with the values ; from the controller JSR UpdateNMITimer ; Update the NMI timer, which we can use in place of ; hardware timers (which the NES does not support) LDA runningSetBank ; If the NMI handler was called from within the SetBank BNE inmi2 ; routine, then runningSetBank will be $FF, so jump to ; inmi2 to skip the call to MakeSounds JSR MakeSounds_b6 ; Call the MakeSounds routine to make the current sounds ; (music and sound effects) LDA nmiStoreA ; Restore the values of A, X and Y that we stored at LDX nmiStoreX ; the start of the NMI handler LDY nmiStoreY RTI ; Return from the interrupt handler .inmi2 INC runningSetBank ; Increment runningSetBank LDA nmiStoreA ; Restore the values of A, X and Y that we stored at LDX nmiStoreX ; the start of the NMI handler LDY nmiStoreY RTI ; Return from the interrupt handler
Name: UpdateNMITimer [Show more] Type: Subroutine Category: Utility routines Summary: Update the NMI timer, which we can use to keep track of time for places like the combat demo
Context: See this subroutine on its own page References: This subroutine is called as follows: * NMI calls UpdateNMITimer
.UpdateNMITimer DEC nmiTimer ; Decrement the NMI timer counter, so that it counts ; each NMI interrupt BNE nmit1 ; If it hasn't reached zero yet, jump to nmit1 to return ; from the subroutine LDA #50 ; Wrap the NMI timer round to start counting down from STA nmiTimer ; 50 once again, as it just reached zero LDA nmiTimerLo ; Increment (nmiTimerHi nmiTimerLo) CLC ADC #1 STA nmiTimerLo LDA nmiTimerHi ADC #0 STA nmiTimerHi .nmit1 RTS ; Return from the subroutine
Name: SendPaletteSprites [Show more] Type: Subroutine Category: Drawing sprites Summary: Send the current palette and sprite data to the PPU
Context: See this subroutine on its own page References: This subroutine is called as follows: * NMI calls SendPaletteSprites
.SendPaletteSprites STA nmiStoreA ; Store the values of A, X and Y so we can retrieve them STX nmiStoreX ; at the end of the NMI handler STY nmiStoreY LDA PPU_STATUS ; Read from PPU_STATUS to clear bit 7 of PPU_STATUS and ; reset the VBlank start flag INC nmiCounter ; Increment the NMI counter so it increments every ; VBlank LDA #0 ; Write 0 to OAM_ADDR so we can use OAM_DMA to send STA OAM_ADDR ; sprite data to the PPU LDA #$02 ; Write $02 to OAM_DMA to upload 256 bytes of sprite STA OAM_DMA ; data from the sprite buffer at $02xx into the PPU LDA #%00000000 ; Configure the PPU by setting PPU_MASK as follows: STA PPU_MASK ; ; * Bit 0 clear = normal colour (not monochrome) ; * Bit 1 clear = hide leftmost 8 pixels of background ; * Bit 2 clear = hide sprites in leftmost 8 pixels ; * Bit 3 clear = hide background ; * Bit 4 clear = hide sprites ; * Bit 5 clear = do not intensify greens ; * Bit 6 clear = do not intensify blues ; * Bit 7 clear = do not intensify reds ; Fall through into SetPaletteForView to send palette 0 ; for the current view to the PPU
Name: SetPaletteForView [Show more] Type: Subroutine Category: Drawing the screen Summary: Send palette 0 for the current view to the PPU Deep dive: Bitplanes in NES Elite Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendBuffersToPPU (Part 2 of 3) calls SetPaletteForView * SendBuffersToPPU (Part 3 of 3) calls SetPaletteForView
.SetPaletteForView LDA QQ11a ; Set A to the current view (or the old view that is ; still being shown, if we are in the process of ; changing view) BNE palv2 ; If this is not the space view, jump to palv2 ; If we get here then this is the space view LDY visibleColour ; Set Y to the colour to use for visible pixels LDA hiddenBitplane ; If hiddenBitplane is non-zero (i.e. 1), jump to palv1 BNE palv1 ; to hide pixels in bitplane 1 ; If we get here then hiddenBitplane = 0, so now we hide ; pixels in bitplane 0 and show pixels in bitplane 1 LDA #$3F ; Set PPU_ADDR = $3F01, so it points to background STA PPU_ADDR ; palette 0 in the PPU LDA #$01 STA PPU_ADDR LDA hiddenColour ; Set A to the colour to use for hidden pixels STA PPU_DATA ; Set palette 0 to the following: STY PPU_DATA ; STY PPU_DATA ; * Colour 0 = background (black) ; ; * Colour 1 = hidden colour (bitplane 0) ; ; * Colour 2 = visible colour (bitplane 1) ; ; * Colour 3 = visible colour ; ; So pixels in bitplane 0 will be hidden, while ; pixels in bitplane 1 will be visible ; ; i.e. pixels in the hiddenBitplane will be hidden LDA #$00 ; Change the PPU address away from the palette entries STA PPU_ADDR ; to prevent the palette being corrupted LDA #$00 STA PPU_ADDR RTS ; Return from the subroutine .palv1 ; If we get here then hiddenBitplane = 1, so now we hide ; pixels in bitplane 1 and show pixels in bitplane 0 LDA #$3F ; Set PPU_ADDR = $3F01, so it points to background STA PPU_ADDR ; palette 0 in the PPU LDA #$01 STA PPU_ADDR LDA hiddenColour ; Set A to the colour to use for hidden pixels STY PPU_DATA ; Set palette 0 to the following: STA PPU_DATA ; STY PPU_DATA ; * Colour 0 = background (black) ; ; * Colour 1 = visible colour (bitplane 0) ; ; * Colour 2 = hidden colour (bitplane 1) ; ; * Colour 3 = visible colour ; ; So pixels in bitplane 0 will be visible, while ; pixels in bitplane 1 will be hidden ; ; i.e. pixels in the hiddenBitplane will be hidden LDA #$00 ; Change the PPU address away from the palette entries STA PPU_ADDR ; to prevent the palette being corrupted LDA #$00 STA PPU_ADDR RTS ; Return from the subroutine .palv2 ; If we get here then this is not the space view CMP #$98 ; If this is the Status Mode screen, jump to palv3 BEQ palv3 ; If we get here then this is not the space view or the ; Status Mode screen LDA #$3F ; Set PPU_ADDR = $3F15, so it points to sprite palette 1 STA PPU_ADDR ; in the PPU LDA #$15 STA PPU_ADDR LDA visibleColour ; Set palette 0 to the following: STA PPU_DATA ; LDA paletteColour2 ; * Colour 0 = background (black) STA PPU_DATA ; LDA paletteColour3 ; * Colour 1 = visible colour STA PPU_DATA ; ; * Colour 2 = paletteColour2 ; ; * Colour 3 = paletteColour3 LDA #$00 ; Change the PPU address away from the palette entries STA PPU_ADDR ; to prevent the palette being corrupted LDA #$00 STA PPU_ADDR RTS ; Return from the subroutine .palv3 ; If we get here then this is the Status Mode screen LDA #$3F ; Set PPU_ADDR = $3F01, so it points to background STA PPU_ADDR ; palette 0 in the PPU LDA #$01 STA PPU_ADDR LDA visibleColour ; Set palette 0 to the following: STA PPU_DATA ; LDA paletteColour2 ; * Colour 0 = background (black) STA PPU_DATA ; LDA paletteColour3 ; * Colour 1 = visible colour STA PPU_DATA ; ; * Colour 2 = paletteColour2 ; ; * Colour 3 = paletteColour3 LDA #$00 ; Change the PPU address away from the palette entries STA PPU_ADDR ; to prevent the palette being corrupted LDA #$00 STA PPU_ADDR RTS ; Return from the subroutine
Name: SendPalettesToPPU [Show more] Type: Subroutine Category: PPU Summary: Send the palette data from XX3 to the PPU
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendScreenToPPU calls SendPalettesToPPU
.SendPalettesToPPU LDA #$3F ; Set PPU_ADDR = $3F01, so it points to background STA PPU_ADDR ; palette 0 in the PPU LDA #$01 STA PPU_ADDR LDX #1 ; We are about to send the palette data from XX3 to ; the PPU, so set an index counter in X so we send the ; following: ; ; XX3+1 goes to $3F01 ; XX3+2 goes to $3F02 ; ... ; XX3+$30 goes to $3F30 ; XX3+$31 goes to $3F31 ; ; So the following loop sends data for the four ; background palettes and the four sprite palettes .sepa1 LDA XX3,X ; Set A to the X-th entry in XX3 AND #%00111111 ; Clear bits 6 and 7 STA PPU_DATA ; Send the palette entry to the PPU INX ; Increment the loop counter CPX #$20 ; Loop back until we have sent XX3+1 through XX3+$1F BNE sepa1 SUBTRACT_CYCLES 559 ; Subtract 559 from the cycle count JMP SendScreenToPPU+4 ; Return to SendScreenToPPU to continue with the next ; instruction following the call to this routine
Name: SendScreenToPPU [Show more] Type: Subroutine Category: PPU Summary: Update the screen with the contents of the buffers Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * NMI calls SendScreenToPPU * SendPalettesToPPU calls via SendScreenToPPU+4

Other entry points: SendScreenToPPU+4 Re-entry point following the call to SendPalettesToPPU at the start of the routine
.SendScreenToPPU LDA updatePaletteInNMI ; If updatePaletteInNMI is non-zero, then jump up to BNE SendPalettesToPPU ; SendPalettesToPPU to send the palette data in XX3 to ; the PPU, before continuing with the next instruction JSR SendBuffersToPPU ; Send the contents of the nametable and pattern buffers ; to the PPU to update the screen JSR SetPPURegisters ; Set PPU_CTRL, PPU_ADDR and PPU_SCROLL for the current ; hidden bitplane LDA cycleCount ; Add 100 ($0064) to cycleCount CLC ADC #$64 STA cycleCount LDA cycleCount+1 ADC #$00 STA cycleCount+1 BMI upsc1 ; If the result is negative, jump to upsc1 to stop ; sending PPU data in this VBlank, as we have run out of ; cycles (we will pick up where we left off in the next ; VBlank) JSR ClearBuffers ; The result is positive, so we have enough cycles to ; keep sending PPU data in this VBlank, so call ; ClearBuffers to reset the buffers for both bitplanes .upsc1 LDA #%00011110 ; Configure the PPU by setting PPU_MASK as follows: STA PPU_MASK ; ; * Bit 0 clear = normal colour (i.e. not monochrome) ; * Bit 1 set = show leftmost 8 pixels of background ; * Bit 2 set = show sprites in leftmost 8 pixels ; * Bit 3 set = show background ; * Bit 4 set = show sprites ; * Bit 5 clear = do not intensify greens ; * Bit 6 clear = do not intensify blues ; * Bit 7 clear = do not intensify reds RTS ; Return from the subroutine
Name: SetPPURegisters [Show more] Type: Subroutine Category: PPU Summary: Set PPU_CTRL, PPU_ADDR and PPU_SCROLL for the current hidden bitplane Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendScreenToPPU calls SetPPURegisters
.SetPPURegisters LDX #%10010000 ; Set X to use as the value of PPU_CTRL for when ; hiddenBitplane is 1: ; ; * Bits 0-1 = base nametable address %00 ($2000) ; * Bit 2 clear = increment PPU_ADDR by 1 each time ; * Bit 3 clear = sprite pattern table is at $0000 ; * Bit 4 set = background pattern table is at $1000 ; * Bit 5 clear = sprites are 8x8 pixels ; * Bit 6 clear = use PPU 0 (the only option on a NES) ; * Bit 7 set = enable VBlank NMI generation LDA hiddenBitplane ; If hiddenBitplane is non-zero (i.e. 1), skip the BNE resp1 ; following LDX #%10010001 ; Set X to use as the value of PPU_CTRL for when ; hiddenBitplane is 0: ; ; * Bits 0-1 = base nametable address %01 ($2400) ; * Bit 2 clear = increment PPU_ADDR by 1 each time ; * Bit 3 clear = sprite pattern table is at $0000 ; * Bit 4 set = background pattern table is at $1000 ; * Bit 5 clear = sprites are 8x8 pixels ; * Bit 6 clear = use PPU 0 (the only option on a NES) ; * Bit 7 set = enable VBlank NMI generation .resp1 STX PPU_CTRL ; Configure the PPU with the above value of PPU_CTRL, ; according to the hidden bitplane, so we set: ; ; * Nametable 0 when hiddenBitplane = 1 ; ; * Nametable 1 when hiddenBitplane = 0 ; ; This makes sure that the screen shows the nametable ; for the visible bitplane, and not the hidden bitplane STX ppuCtrlCopy ; Store the new value of PPU_CTRL in ppuCtrlCopy so we ; can check its value without having to access the PPU LDA #$20 ; If hiddenBitplane = 0 then set A = $24, otherwise set LDX hiddenBitplane ; A = $20, to use as the high byte of the PPU_ADDR BNE resp2 ; address LDA #$24 .resp2 STA PPU_ADDR ; Set PPU_ADDR to point to the nametable address that we LDA #$00 ; just configured: STA PPU_ADDR ; ; * $2000 (nametable 0) when hiddenBitplane = 1 ; ; * $2400 (nametable 1) when hiddenBitplane = 0 ; ; So we now flush the pipeline for the nametable that we ; are showing on-screen, to avoid any corruption LDA PPU_DATA ; Read from PPU_DATA eight times to clear the pipeline LDA PPU_DATA ; and reset the internal PPU read buffer LDA PPU_DATA LDA PPU_DATA LDA PPU_DATA LDA PPU_DATA LDA PPU_DATA LDA PPU_DATA LDA #8 ; Set the horizontal scroll to 8, so the leftmost tile STA PPU_SCROLL ; on each row is scrolled around to the right side ; ; This means that in terms of tiles, column 1 is the ; left edge of the screen, then columns 2 to 31 form the ; body of the screen, and column 0 is the right edge of ; the screen LDA #0 ; Set the vertical scroll to 0 STA PPU_SCROLL RTS ; Return from the subroutine
Name: SetPPUTablesTo0 [Show more] Type: Subroutine Category: PPU Summary: Set nametable 0 and pattern table 0 for drawing the icon bar Deep dive: The split-screen mode in NES Elite
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.SetPPUTablesTo0 LDA #0 ; Clear bit 7 of setupPPUForIconBar, so this routine STA setupPPUForIconBar ; doesn't get called again until the next NMI interrupt ; at the next VBlank (as the SETUP_PPU_FOR_ICON_BAR ; macro and SetupPPUForIconBar routine only update the ; PPU when bit 7 is set) LDA ppuCtrlCopy ; Set A to the current value of PPU_CTRL AND #%11101110 ; Clear bits 0 and 4, which will set the base nametable ; address to $2000 (for nametable 0) and the pattern ; table address to $0000 (for pattern table 0) STA PPU_CTRL ; Update PPU_CTRL to set nametable 0 and pattern table 0 STA ppuCtrlCopy ; Store the new value of PPU_CTRL in ppuCtrlCopy so we ; can check its value without having to access the PPU CLC ; Clear the C flag RTS ; Return from the subroutine
Name: ClearBuffers [Show more] Type: Subroutine Category: Drawing the screen Summary: If there are enough free cycles, clear down the nametable and pattern buffers for both bitplanes Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendScreenToPPU calls ClearBuffers
.ClearBuffers LDA cycleCount+1 ; If the high byte of cycleCount(1 0) is zero, then the BEQ cbuf3 ; cycle count is 255 or less, so jump to cbuf3 to skip ; the buffer clearing, as we have run out of cycles (we ; will pick up where we left off in the next VBlank) SUBTRACT_CYCLES 363 ; Subtract 363 from the cycle count BMI cbuf1 ; If the result is negative, jump to cbuf1 to skip the ; buffer clearing, as we have run out of cycles (we ; will pick up where we left off in the next VBlank) JMP cbuf2 ; The result is positive, so we have enough cycles to ; clear the buffers, so jump to cbuf2 to do just that .cbuf1 ADD_CYCLES 318 ; Add 318 to the cycle count JMP cbuf3 ; Jump to cbuf3 to skip the buffer clearing and return ; from the subroutine .cbuf2 LDA clearBlockSize ; Store clearBlockSize(1 0) and clearAddress(1 0) on the PHA ; stack, so we can use them in the ClearPlaneBuffers LDA clearBlockSize+1 ; routine and can restore them to their original values PHA ; afterwards (in case the NMI handler was called while LDA clearAddress ; these variables are being used) PHA LDA clearAddress+1 PHA LDX #0 ; Call ClearPlaneBuffers with X = 0 to clear the buffers JSR ClearPlaneBuffers ; for bitplane 0 LDX #1 ; Call ClearPlaneBuffers with X = 1 to clear the buffers JSR ClearPlaneBuffers ; for bitplane 1 PLA ; Retore clearBlockSize(1 0) and clearAddress(1 0) from STA clearAddress+1 ; the stack PLA STA clearAddress PLA STA clearBlockSize+1 PLA STA clearBlockSize ADD_CYCLES_CLC 238 ; Add 238 to the cycle count .cbuf3 ; This part of the routine repeats the code in cbuf5 ; until we run out of cycles, though as cbuf5 only ; contains NOPs, this doesn't achieve anything other ; than running down the cycle counter (perhaps it's ; designed to even out each call to the NMI handler, ; or is just left over from development) SUBTRACT_CYCLES 32 ; Subtract 32 from the cycle count BMI cbuf4 ; If the result is negative, jump to cbuf4 to return ; from the subroutine, as we have run out of cycles JMP cbuf5 ; The result is positive, so we have enough cycles to ; continue, so jump to cbuf5 .cbuf4 ADD_CYCLES 65527 ; Add 65527 to the cycle count (i.e. subtract 9) JMP cbuf6 ; Jump to cbuf6 to return from the subroutine .cbuf5 NOP ; This looks like code that has been removed NOP NOP JMP cbuf3 ; Jump back to cbuf3 to check the cycle count and keep ; running the above until the cycle count runs out .cbuf6 RTS ; Return from the subroutine
Name: ReadControllers [Show more] Type: Subroutine Category: Controllers Summary: Read the buttons on the controllers and update the control variables Deep dive: Bolting NES controllers onto the key logger
Context: See this subroutine on its own page References: This subroutine is called as follows: * NMI calls ReadControllers
.ReadControllers LDA #1 ; Write 1 then 0 to the controller port at JOY1 to tell STA JOY1 ; the controllers to latch the button positions, so we LSR A ; can then read them in the ScanButtons routine STA JOY1 TAX ; Call ScanButtons with X = 0 to scan controller 1 and JSR ScanButtons ; update the controller variables LDX numberOfPilots ; Set X to numberOfPilots, which will be 0 if only one ; pilot is configured in the pause options, or 1 if two ; pilots are configured BEQ RTS3 ; If X = 0 then only one pilot is configured, so jump to ; RTS3 to return from the subroutine, as we do not need ; to scan controller 2 ; Otherwise X = 1 and two pilots are configured, so fall ; through into ScanButtons with X = 1 to scan controller ; 2 and update the control variables
Name: ScanButtons [Show more] Type: Subroutine Category: Controllers Summary: Scan a specific controller and update the control variables Deep dive: Bolting NES controllers onto the key logger
Context: See this subroutine on its own page References: This subroutine is called as follows: * ReadControllers calls ScanButtons * ReadControllers calls via RTS3

Arguments: X The controller to scan: * 0 = scan controller 1 * 1 = scan controller 2
Other entry points: RTS3 Contains an RTS
.ScanButtons LDA JOY1,X ; Read the status of the A button on controller X, and AND #%00000011 ; if it is being pressed, shift a 1 into bit 7 of CMP #%00000001 ; controller1A (as A = 1), otherwise shift a 0 ROR controller1A,X LDA JOY1,X ; Read the status of the B button on controller X, and AND #%00000011 ; if it is being pressed, shift a 1 into bit 7 of CMP #%00000001 ; controller1B (as A = 1), otherwise shift a 0 ROR controller1B,X LDA JOY1,X ; Read the status of the Select button on controller AND #%00000011 ; X, and if it is being pressed, shift a 1 into bit 7 of CMP #%00000001 ; controller1Select (as A = 1), otherwise shift a 0 ROR controller1Select,X LDA JOY1,X ; Read the status of the Start button on controller AND #%00000011 ; X, and if it is being pressed, shift a 1 into bit 7 of CMP #%00000001 ; controller1Start (as A = 1), otherwise shift a 0 ROR controller1Start,X LDA JOY1,X ; Read the status of the up button on controller X, AND #%00000011 ; and if it is being pressed, shift a 1 into bit 7 of CMP #%00000001 ; controller1Up (as A = 1), otherwise shift a 0 ROR controller1Up,X LDA JOY1,X ; Read the status of the down button on controller AND #%00000011 ; X, and if it is being pressed, shift a 1 into bit 7 of CMP #%00000001 ; controller1Down (as A = 1), otherwise shift a 0 ROR controller1Down,X LDA JOY1,X ; Read the status of the left button on controller AND #%00000011 ; X, and if it is being pressed, shift a 1 into bit 7 of CMP #%00000001 ; controller1Left (as A = 1), otherwise shift a 0 ROR controller1Left,X LDA JOY1,X ; Read the status of the right button on controller AND #%00000011 ; X, and if it is being pressed, shift a 1 into bit 7 of CMP #%00000001 ; controller1Right (as A = 1), otherwise shift a 0 ROR controller1Right,X .RTS3 RTS ; Return from the subroutine
Name: WaitForNextNMI [Show more] Type: Subroutine Category: Utility routines Summary: An unused routine that waits until the NMI counter increments (i.e. the next VBlank)
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.WaitForNextNMI LDA nmiCounter ; Set A to the NMI counter, which increments with each ; call to the NMI handler .wfrm1 CMP nmiCounter ; Loop back to wfrm1 until the NMI counter changes, BEQ wfrm1 ; which will happen when the NMI handler has been called ; again (i.e. at the next VBlank) RTS ; Return from the subroutine
Name: WaitFor2NMIs [Show more] Type: Subroutine Category: Utility routines Summary: Wait until two NMI interrupts have passed (i.e. the next two VBlanks)
Context: See this subroutine on its own page References: This subroutine is called as follows: * FadeToBlack calls WaitFor2NMIs * FadeToColour calls WaitFor2NMIs

Returns: A A is preserved
.WaitFor2NMIs JSR WaitForNMI ; Wait until the next NMI interrupt has passed (i.e. the ; next VBlank) ; Fall through into WaitForNMI to wait for the second ; NMI interrupt
Name: WaitForNMI [Show more] Type: Subroutine Category: Utility routines Summary: Wait until the next NMI interrupt has passed (i.e. the next VBlank)
Returns: A A is preserved
.WaitForNMI PHA ; Store A on the stack to preserve it LDX nmiCounter ; Set X to the NMI counter, which increments with each ; call to the NMI handler .wnmi1 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 CPX nmiCounter ; Loop back to wnmi1 until the NMI counter changes, BEQ wnmi1 ; which will happen when the NMI handler has been called ; again (i.e. at the next VBlank) PLA ; Retrieve A from the stack so that it's preserved RTS ; Return from the subroutine
Name: WaitForIconBarPPU [Show more] Type: Subroutine Category: PPU Summary: Wait until the PPU starts drawing the icon bar
Context: See this subroutine on its own page References: This subroutine is called as follows: * PrintEquipment calls WaitForIconBarPPU
.WaitForIconBarPPU LDA setupPPUForIconBar ; Loop back to the start until setupPPUForIconBar is BEQ WaitForIconBarPPU ; non-zero, at which point the SETUP_PPU_FOR_ICON_BAR ; macro and SetupPPUForIconBar routine are checking to ; see whether the icon bar is being drawn by the PPU .wbar1 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 setupPPUForIconBar ; Loop back until setupPPUForIconBar is zero, at which BNE wbar1 ; point the icon bar is being drawn by the PPU RTS ; Return from the subroutine
Name: ClearDrawingPlane (Part 1 of 3) [Show more] Type: Subroutine Category: Drawing the screen Summary: Clear the nametable and pattern buffers for the newly flipped drawing plane Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * FlipDrawingPlane calls ClearDrawingPlane

This routine is only called when we have just flipped the drawing plane between 0 and 1 in the FlipDrawingPlane routine.
Arguments: X The drawing bitplane to clear
LDX #0 ; This code is never called, but it provides an entry JSR ClearDrawingPlane ; point for clearing both bitplanes, which would have LDX #1 ; been useful during development .ClearDrawingPlane 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 bitplaneFlags,X ; If the flags for the new drawing bitplane are zero BEQ cdra2 ; then the bitplane's buffers are already clear (as we ; will have zeroed the flags in cdra1 following a ; successful clearance), so jump to cdra2 to return ; from the subroutine AND #%00100000 ; If bit 5 of the bitplane flags is set, then we have BNE cdra1 ; already sent all the data to the PPU for this ; bitplane, so jump to cdra1 to clear the buffers in ; their entirety JSR cdra3 ; If we get here then bit 5 of the bitplane flags is ; clear, which means we have not already sent all the ; data to the PPU for this bitplane, so call cdra3 below ; to clear out as much buffer space as we can for now JMP ClearDrawingPlane ; Jump back to the start of the routine so we keep ; clearing as much buffer space as we can until all the ; data has been sent to the PPU (at which point bit 5 ; will be set and we will take the cdra1 branch instead) .cdra1 JSR cdra3 ; If we get here then bit 5 of the bitplane flags is ; set, which means we have already sent all the data to ; the PPU for this bitplane, so call cdra3 below to ; clear out all remaining buffer space for this bitplane LDA #0 ; Set the new drawing bitplane flags as follows: STA bitplaneFlags,X ; ; * 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 clear = only send pattern data to the PPU ; * Bit 7 clear = do not send data to the PPU ; ; Bits 0 and 1 are ignored and are always clear 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 JMP DrawBoxTop ; Draw the top of the box into the new drawing bitplane, ; returning from the subroutine using a tail call .cdra2 RTS ; Return from the subroutine
Name: ClearDrawingPlane (Part 2 of 3) [Show more] Type: Subroutine Category: Drawing the screen Summary: Clear the nametable buffers for the newly flipped drawing plane Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.cdra3 LDY nmiCounter ; Set Y to the NMI counter, which is incremented every ; VBlank by the NMI handler LDA sendingNameTile,X ; Set SC to sendingNameTile for this bitplane, which STA SC ; contains the number of the last tile that was sent to ; the PPU nametable by the NMI handler, divided by 8 ; ; So this contains the number of the last tile we need ; to clear in the nametable buffer, divided by 8 LDA clearingNameTile,X ; Set A to clearingNameTile for this bitplane, which ; contains the number of the first tile we need to ; clear in the nametable buffer, divided by 8 CPY nmiCounter ; If the NMI counter has incremented since we fetched it BNE cdra3 ; above, then the tile numbers we just fetched might ; already be out of date (as the NMI handler runs at ; every VBlank, so it may have been run between now and ; the nmiCounter fetch above), so jump back to cdra3 ; to fetch them all again LDY SC ; Set Y to the number of the last tile divided by 8, ; which we fetched above CPY maxNameTileToClear ; If Y >= maxNameTileToClear then set Y to the value of BCC cdra4 ; maxNameTileToClear, so Y is capped to a maximum value LDY maxNameTileToClear ; of maxNameTileToClear .cdra4 STY SC ; Set SC to the number of the last tile, capped by the ; maximum value in maxNameTileToClear CMP SC ; If A >= SC then the first tile we need to clear is BCS cdra6 ; after the last tile we need to clear, which means ; there are no nametable tiles to clear, so jump to ; to cdra6 to move on to clearing the pattern buffer ; in part 3 STY clearingNameTile,X ; Set clearingNameTile to the number of the last tile ; to clear, if we don't clear the whole buffer here ; (which will be the case if the buffer is still being ; sent to the PPU), then we can pick it up again from ; the tile after the batch we are about to clear LDY #0 ; Set clearAddress(1 0) = (nameBufferHiAddr 0) + A * 8 STY clearAddress+1 ; = (nameBufferHiAddr 0) + first tile ASL A ; ROL clearAddress+1 ; So clearAddress(1 0) contains the address in this ASL A ; bitplane's nametable buffer of the first tile we sent ROL clearAddress+1 ASL A STA clearAddress LDA clearAddress+1 ROL A ADC nameBufferHiAddr,X STA clearAddress+1 LDA #0 ; Set SC(1 0) = (0 SC) * 8 + (nameBufferHiAddr 0) ASL SC ; ROL A ; So SC(1 0) contains the address in this bitplane's ASL SC ; nametable buffer of the last tile we sent ROL A ASL SC ROL A ADC nameBufferHiAddr,X STA SC+1 .cdra5 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 SC ; Set clearBlockSize(1 0) = SC(1 0) - clearAddress(1 0) SEC ; SBC clearAddress ; So clearBlockSize(1 0) contains the number of tiles we STA clearBlockSize ; already sent from this bitplane's nametable buffer LDA SC+1 ; SBC clearAddress+1 ; If the subtraction underflows, then there are no tiles BCC cdra6 ; to send, so jump to cdra6 to move on to clearing the STA clearBlockSize+1 ; pattern buffer in part 3 ; By this point, clearBlockSize(1 0) contains the number ; of tiles we sent from this bitplane's nametable ; buffer, so it contains the number of nametable entries ; we need to clear ; ; Also, clearAddress(1 0) contains the address of the ; first tile we sent from this bitplane's nametable ; buffer ORA clearBlockSize ; If both the high and low bytes of clearBlockSize(1 0) BEQ cdra6 ; are zero, then there are no tiles to clear, so jump to ; cdra6 to clear the pattern buffer LDA #HI(790) ; Set cycleCount = 790, so the call to ClearMemory STA cycleCount+1 ; doesn't run out of cycles and quit early (we are not LDA #LO(790) ; in the NMI handler, so we don't need to count cycles, STA cycleCount ; so this just ensures that the cycle-counting checks ; are not triggered) JSR ClearMemory ; Call ClearMemory to zero clearBlockSize(1 0) nametable ; entries from address clearAddress(1 0) onwards JMP cdra5 ; The above should clear the whole block, but if the NMI ; handler is called at VBlank while we are doing this, ; then cycleCount may end up ticking down to zero while ; we are still clearing memory, which would abort the ; call to ClearMemory early, so we now loop back to ; cdra5 to pick up where we left off, eventually exiting ; the loop via the BCC cdra6 instruction above (at which ; point we know for sure that we have cleared the whole ; block)
Name: ClearDrawingPlane (Part 3 of 3) [Show more] Type: Subroutine Category: Drawing the screen Summary: Clear the pattern buffers for the newly flipped drawing plane Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.cdra6 LDY nmiCounter ; Set Y to the NMI counter, which is incremented every ; VBlank by the NMI handler LDA sendingPattern,X ; Set SC to sendingPattern for this bitplane, which STA SC ; contains the number of the last pattern that was sent ; to the PPU pattern table by the NMI handler ; ; So this contains the number of the last pattern we ; need to clear in the pattern buffer LDA clearingPattern,X ; Set A to clearingPattern for this bitplane, which ; contains the number of the first pattern we need ; to clear in the pattern buffer CPY nmiCounter ; If the NMI counter has incremented since we fetched it BNE cdra6 ; above, then the pattern numbers we just fetched might ; already be out of date (as the NMI handler runs at ; every VBlank, so it may have been run between now and ; the nmiCounter fetch above), so jump back to cdra6 ; to fetch them all again LDY SC ; Set Y to the number of the last pattern, which we ; fetched above CMP SC ; If A >= SC then the first pattern we need to clear is BCS cdra8 ; after the last pattern we need to clear, which means ; there are no pattern entries to clear, so jump to ; to cdra8 to return from the subroutine as we are done STY clearingPattern,X ; Set clearingPattern to the number of the last pattern ; to clear, if we don't clear the whole buffer here ; (which will be the case if the buffer is still being ; sent to the PPU), then we can pick it up again from ; the pattern after the batch we are about to clear LDY #0 ; Set clearAddress(1 0) = (pattBufferHiAddr 0) + A * 8 STY clearAddress+1 ; = (pattBufferHiAddr 0) + first pattern ASL A ; ROL clearAddress+1 ; So clearAddress(1 0) contains the address in this ASL A ; bitplane's pattern buffer of the first pattern we sent ROL clearAddress+1 ASL A STA clearAddress LDA clearAddress+1 ROL A ADC pattBufferHiAddr,X STA clearAddress+1 LDA #0 ; Set SC(1 0) = (0 SC) * 8 + (pattBufferHiAddr 0) ASL SC ; ROL A ; So SC(1 0) contains the address in this bitplane's ASL SC ; pattern buffer of the last pattern we sent ROL A ASL SC ROL A ADC pattBufferHiAddr,X STA SC+1 .cdra7 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 SC ; Set clearBlockSize(1 0) = SC(1 0) - clearAddress(1 0) SEC ; SBC clearAddress ; So clearBlockSize(1 0) contains the number of patterns STA clearBlockSize ; we already sent from this bitplane's pattern buffer LDA SC+1 ; SBC clearAddress+1 ; If the subtraction underflows, then there are no BCC cdra6 ; patterns to send, so jump to cdra6 to make sure we STA clearBlockSize+1 ; have cleared the whole pattern buffer ; By this point, clearBlockSize(1 0) contains the number ; of patterns we sent from this bitplane's pattern ; buffer, so it contains the number of pattern entries ; we need to clear ; ; Also, clearAddress(1 0) contains the address of the ; first pattern we sent from this bitplane's pattern ; buffer ORA clearBlockSize ; If both the high and low bytes of clearBlockSize(1 0) BEQ cdra8 ; are zero, then there are no patterns to clear, so jump ; to cdra8 to return from the subroutine, as we are done LDA #HI(790) ; Set cycleCount = 790, so the call to ClearMemory STA cycleCount+1 ; doesn't run out of cycles and quit early (we are not LDA #LO(790) ; in the NMI handler, so we don't need to count cycles, STA cycleCount ; so this just ensures that the cycle-counting checks ; are not triggered) JSR ClearMemory ; Call ClearMemory to zero clearBlockSize(1 0) nametable ; entries from address clearAddress(1 0) onwards JMP cdra7 ; The above should clear the whole block, but if the NMI ; handler is called at VBlank while we are doing this, ; then cycleCount may end up ticking down to zero while ; we are still clearing memory, which would abort the ; call to ClearMemory early, so we now loop back to ; cdra7 to pick up where we left off, eventually exiting ; the loop via the BCC cdra6 instruction above (at which ; point we know for sure that we have cleared the whole ; block) .cdra8 RTS ; Return from the subroutine
Name: flagsForClearing [Show more] Type: Variable Category: Drawing the screen Summary: A bitplane mask to control how bitplane buffer clearing works in the ClearPlaneBuffers routine
Context: See this variable on its own page References: This variable is used as follows: * ClearPlaneBuffers (Part 1 of 2) uses flagsForClearing
.flagsForClearing EQUB %00110000 ; The bitplane flags with ones in this byte must be ; clear for the clearing process in ClearPlaneBuffers ; to be activated ; ; So this configuration means that clearing will only be ; attempted on bitplanes where: ; ; * We are in the process of sending this bitplane's ; data to the PPU (bit 4 is set) ; ; * We have already sent all the data to the PPU for ; this bitplane (bit 5 is set) ; ; If both bitplane flags are clear, then the buffers are ; not cleared ; ; Note that this is separate from bit 3, which controls ; whether clearing is enabled and which overrides the ; above (bit 2 must be set for any clearing to take ; place)
Name: ClearPlaneBuffers (Part 1 of 2) [Show more] Type: Subroutine Category: Drawing the screen Summary: Clear the nametable and pattern buffers of data that has already been sent to the PPU, starting with the nametable buffer Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * ClearBuffers calls ClearPlaneBuffers

Arguments: X The bitplane to clear
.pbuf1 NOP ; This looks like code that has been removed NOP .pbuf2 SUBTRACT_CYCLES 39 ; Subtract 39 from the cycle count .pbuf3 RTS ; Return from the subroutine .pbuf4 ADD_CYCLES_CLC 126 ; Add 126 to the cycle count JMP pbuf13 ; Jump to pbuf13 in part 2 to consider clearing the ; pattern buffer .ClearPlaneBuffers LDA cycleCount+1 ; If the high byte of cycleCount(1 0) is zero, then the BEQ pbuf3 ; cycle count is 255 or less, so jump to pbuf3 to skip ; the buffer clearing, as we have run out of cycles (we ; will pick up where we left off in the next VBlank) LDA bitplaneFlags,X ; If both bits 4 and 5 of the current bitplane flags are BIT flagsForClearing ; clear, then this means: BEQ pbuf1 ; ; * Bit 4 clear = we've not started sending data yet ; * Bit 5 clear = we have not yet sent all the data ; ; So we are not currently sending tile data to the PPU ; for this bitplane, and we have not already sent the ; data, so we do not need to clear this bitplane as we ; only do so after sending its data to the PPU, which ; we are not currently doing AND #%00001000 ; If bit 3 of the of the current bitplane flags is BEQ pbuf2 ; clear, then this bitplane is configured not to be ; cleared after it has been sent to the PPU, so jump to ; pbuf2 to return from the subroutine without clearing ; the buffers ; If we get here then we are either in the process of ; sending this bitplane's data to the PPU, or we have ; already sent it, and the bitplane is configured to be ; cleared ; ; If we have already sent the data to the PPU, then we ; no longer need it, so we need to clear the buffers so ; they are blank and ready to be drawn for the next ; frame ; ; If we are still in the process of sending this ; bitplane's data to the PPU, then we can clear the ; buffers up to the point where we have sent the data, ; as we don't need to keep any data that we have sent ; ; The following routine clears the buffers from the ; first tile we sent, up to the tile and pattern numbers ; given by sendingNameTile and sendingPattern, which ; will work in both cases, whether or not we have ; finished sending all the data to the PPU SUBTRACT_CYCLES 213 ; Subtract 213 from the cycle count BMI pbuf5 ; If the result is negative, jump to pbuf5 to skip the ; buffer clearing, as we have run out of cycles (we ; will pick up where we left off in the next VBlank) JMP pbuf6 ; The result is positive, so we have enough cycles to ; clear the buffers, so jump to pbuf6 to do just that .pbuf5 ADD_CYCLES 153 ; Add 153 to the cycle count JMP pbuf3 ; Jump to pbuf3 to skip the buffer clearing and return ; from the subroutine .pbuf6 LDA clearingNameTile,X ; Set A to clearingNameTile for this bitplane, which ; contains the number of the first tile we need to ; clear in the nametable buffer, divided by 8 LDY sendingNameTile,X ; Set Y to sendingNameTile for this bitplane, which we ; used in SendNametableToPPU to keep track of the ; current tile number as we sent them to the PPU ; nametable, so this contains the number of the last ; tile, divided by 8, that we sent to the PPU nametable ; for this bitplane ; ; So this contains the number of the last tile we need ; to clear in the nametable buffer, divided by 8 CPY maxNameTileToClear ; If Y >= maxNameTileToClear then set Y to the value of BCC pbuf7 ; maxNameTileToClear, so Y is capped to a maximum value LDY maxNameTileToClear ; of maxNameTileToClear .pbuf7 STY clearBlockSize ; Set clearBlockSize to the number of the last tile we ; need to clear, divided by 8 CMP clearBlockSize ; If A >= clearBlockSize, then the first tile we need to BCS pbuf4 ; clear is after the last tile we need to clear, which ; means there are no nametable tiles to clear, so jump ; to pbuf4 to move on to clearing the pattern buffer in ; part 2 LDY #0 ; Set clearAddress(1 0) = (nameBufferHiAddr 0) + A * 8 STY clearAddress+1 ; = (nameBufferHiAddr 0) + first tile ASL A ; ROL clearAddress+1 ; So clearAddress(1 0) contains the address of the first ASL A ; tile we sent in this bitplane's nametable buffer ROL clearAddress+1 ASL A STA clearAddress LDA clearAddress+1 ROL A ADC nameBufferHiAddr,X STA clearAddress+1 LDA #0 ; Set clearBlockSize(1 0) = (0 clearBlockSize) * 8 ASL clearBlockSize ; + (nameBufferHiAddr 0) ROL A ; = (nameBufferHiAddr 0) + last tile ASL clearBlockSize ; ROL A ; So clearBlockSize(1 0) points to the address of the ASL clearBlockSize ; last tile we sent in this bitplane's nametable buffer ROL A ADC nameBufferHiAddr,X STA clearBlockSize+1 LDA clearBlockSize ; Set clearBlockSize(1 0) SEC ; = clearBlockSize(1 0) - clearAddress(1 0) SBC clearAddress ; STA clearBlockSize ; So clearBlockSize(1 0) contains the number of tiles we LDA clearBlockSize+1 ; already sent from this bitplane's nametable buffer SBC clearAddress+1 ; BCC pbuf8 ; If the subtraction underflows, then there are no tiles STA clearBlockSize+1 ; to send, so jump to pbuf8 to move on to clearing the ; pattern buffer in part 2 ; By this point, clearBlockSize(1 0) contains the number ; of tiles we sent from this bitplane's nametable ; buffer, so it contains the number of nametable entries ; we need to clear ; ; Also, clearAddress(1 0) contains the address of the ; first tile we sent from this bitplane's nametable ; buffer ORA clearBlockSize ; If both the high and low bytes of clearBlockSize(1 0) BEQ pbuf9 ; are zero, then there are no tiles to clear, so jump to ; pbuf9 and on to part 2 to consider clearing the ; pattern buffer JSR ClearMemory ; Call ClearMemory to zero clearBlockSize(1 0) nametable ; entries from address clearAddress(1 0) onwards ; ; If we run out of cycles in the current VBlank, then ; this may not clear the whole block, so it updates ; clearBlockSize(1 0) and clearAddress(1 0) as it clears ; so we can pick it up in the next VBlank LDA clearAddress+1 ; Set (A clearAddress) SEC ; = clearAddress(1 0) - (nameBufferHiAddr 0) SBC nameBufferHiAddr,X LSR A ; Set A to the bottom byte of (A clearAddress) / 8 ROR clearAddress ; LSR A ; This effectively reverses the calculation we did ROR clearAddress ; above, so A contains the number of the next tile LSR A ; we need to clear, as returned by ClearMemory, divided LDA clearAddress ; by 8 ROR A ; ; We only need to take the low byte, as we know the high ; byte will be zero after this many shifts, as that's ; how we built the value of clearAddress(1 0) above CMP clearingNameTile,X ; If A >= clearingNameTile then we did manage to clear BCC pbuf12 ; some nametable entries in ClearMemory, so update the STA clearingNameTile,X ; value of clearingNameTile with the new first tile ; number so the next call to this routine will pick up ; where we left off JMP pbuf13 ; Jump to pbuf13 in part 2 to consider clearing the ; pattern buffer .pbuf8 NOP ; This looks like code that has been removed NOP NOP NOP .pbuf9 ADD_CYCLES_CLC 28 ; Add 28 to the cycle count JMP pbuf13 ; Jump to pbuf13 in part 2 to consider clearing the ; pattern buffer .pbuf10 ADD_CYCLES_CLC 126 ; Add 126 to the cycle count .pbuf11 RTS ; Return from the subroutine
Name: ClearPlaneBuffers (Part 2 of 2) [Show more] Type: Subroutine Category: Drawing the screen Summary: Clear the pattern buffer of data that has already been sent to the PPU for the current bitplane Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.pbuf12 NOP ; This looks like code that has been removed NOP NOP .pbuf13 SUBTRACT_CYCLES 187 ; Subtract 187 from the cycle count BMI pbuf14 ; If the result is negative, jump to pbuf14 to skip the ; pattern buffer clearing, as we have run out of cycles ; (we will pick up where we left off in the next VBlank) JMP pbuf15 ; The result is positive, so we have enough cycles to ; clear the pattern buffer, so jump to pbuf15 to do just ; that .pbuf14 ADD_CYCLES 146 ; Add 146 to the cycle count JMP pbuf11 ; Jump to pbuf11 to return from the subroutine .pbuf15 LDA clearingPattern,X ; Set A to clearingPattern for this bitplane, which ; contains the number of the first pattern we need ; to clear in the pattern buffer LDY sendingPattern,X ; Set Y to sendingPattern for this bitplane, which we ; used in SendPatternsToPPU to keep track of the current ; pattern number as we sent them to the PPU pattern ; table, so this contains the number of the last pattern ; that we sent to the PPU pattern table for this ; bitplane ; ; So this contains the number of the last tile we need ; to clear in the nametable buffer STY clearBlockSize ; Set clearBlockSize to the number of the last tile we ; need to clear CMP clearBlockSize ; If A >= clearBlockSize, then the first tile we need to BCS pbuf10 ; clear is after the last tile we need to clear, which ; means there are no nametable tiles to clear, so jump ; to pbuf10 to return from the subroutine NOP ; This looks like code that has been removed LDY #0 ; Set clearAddress(1 0) = (pattBufferHiAddr 0) + A * 8 STY clearAddress+1 ; = (pattBufferHiAddr 0) + first tile ASL A ; ROL clearAddress+1 ; So clearAddress(1 0) contains the address of the first ASL A ; tile we sent in this bitplane's pattern buffer ROL clearAddress+1 ASL A STA clearAddress LDA clearAddress+1 ROL A ADC pattBufferHiAddr,X STA clearAddress+1 LDA #0 ; Set clearBlockSize(1 0) = (0 clearBlockSize) * 8 ASL clearBlockSize ; + (pattBufferHiAddr 0) ROL A ; = (pattBufferHiAddr 0) + last tile ASL clearBlockSize ; ROL A ; So clearBlockSize(1 0) points to the address of the ASL clearBlockSize ; last tile we sent in this bitplane's pattern buffer ROL A ADC pattBufferHiAddr,X STA clearBlockSize+1 LDA clearBlockSize ; Set clearBlockSize(1 0) SEC ; = clearBlockSize(1 0) - clearAddress(1 0) SBC clearAddress ; STA clearBlockSize ; So clearBlockSize(1 0) contains the number of tiles we LDA clearBlockSize+1 ; already sent from this bitplane's pattern buffer SBC clearAddress+1 BCC pbuf16 STA clearBlockSize+1 ORA clearBlockSize BEQ pbuf17 JSR ClearMemory ; Call ClearMemory to zero clearBlockSize(1 0) pattern ; buffer bytes from address clearAddress(1 0) onwards LDA clearAddress+1 ; Set (A clearAddress) SEC ; = clearAddress(1 0) - (pattBufferHiAddr 0) SBC pattBufferHiAddr,X LSR A ; Set A to the bottom byte of (A clearAddress) / 8 ROR clearAddress ; LSR A ; This effectively reverses the calculation we did ROR clearAddress ; above, so A contains the number of the next tile LSR A ; we need to clear, as returned by ClearMemory, divided LDA clearAddress ; by 8 ROR A ; ; We only need to take the low byte, as we know the high ; byte will be zero after this many shifts, as that's ; how we built the value of clearAddress(1 0) above CMP clearingPattern,X ; If A >= clearingPattern then we did manage to clear BCC pbuf16 ; some pattern bytes in ClearMemory, so update the STA clearingPattern,X ; value of clearingPattern with the new first pattern ; number so the next call to this routine will pick up ; where we left off RTS ; Return from the subroutine .pbuf16 NOP ; This looks like code that has been removed NOP NOP NOP RTS ; Return from the subroutine .pbuf17 ADD_CYCLES_CLC 35 ; Add 35 to the cycle count RTS ; Return from the subroutine
Name: FillMemory [Show more] Type: Subroutine Category: Utility routines Summary: Fill a block of memory with a specified value
Context: See this subroutine on its own page References: This subroutine is called as follows: * ClearMemory calls FillMemory

When called directly, this routine fills a whole page of memory (256 bytes) with the value in A. It can also be called at an arbitrary entry point to fill a specified number of locations, anywhere from 0 to 255 bytes. The entry point is calculated as as an offset backwards from the end of the FillMemory32Bytes routine (which ends at ClearMemory), such that jumping to this entry point will fill the required number of bytes. Each FILL_MEMORY macro call takes up three bytes (two bytes for the STA (clearAddress),Y and one for the INY), so the calculation is essentially: ClearMemory - 1 - (3 * clearBlockSize) where clearBlockSize is the size of the block to clear, in bytes. See the ClearMemory routine for an example of this calculation in action.
Arguments: clearAddress(1 0) The base address of the block of memory to fill Y The index into clearAddress(1 0) from which to fill A The value to fill
Returns: Y The index in Y is updated to point to the byte after the filled block
.FillMemory FILL_MEMORY 224 ; Fill 224 bytes at clearAddress(1 0) + Y with A ; Falling through into FillMemory32Bytes to fill another ; 32 bytes, bringing the total to 256
Name: FillMemory32Bytes [Show more] Type: Subroutine Category: Utility routines Summary: Fill a 32-byte block of memory with a specified value
Context: See this subroutine on its own page References: This subroutine is called as follows: * ClearMemory calls FillMemory32Bytes

Arguments: clearAddress(1 0) The base address of the block of memory to fill Y The index into clearAddress(1 0) from which to fill A The value to fill
Returns: Y The index in Y is updated to point to the byte after the filled block
.FillMemory32Bytes FILL_MEMORY 32 ; Fill 32 bytes at clearAddress(1 0) + Y with A RTS ; Return from the subroutine