Skip to navigation

Elite on the BBC Micro and NES

Bank 7 (Part 2 of 4)

[NES version]

Name: ClearMemory [Show more] Type: Subroutine Category: Utility routines Summary: Clear a block of memory, split across multiple calls if required
This routine clears a block of memory, but only if there are enough cycles in the cycle count. If it runs out of cycles, it will pick up where it left off when called again.
Arguments: clearAddress The address of the block to clear clearBlockSize The size of the block to clear as a 16-bit number, must be a multiple of 8 bytes
Returns: clearAddress The address of the next byte to clear in the block, ready for the next call (if the whole block was not cleared) clearBlockSize The size of the block, reduced by the number of bytes cleared in the current call, so it's ready for the next call (this will be 0 if this call cleared the whole block)
.ClearMemory LDA clearBlockSize+1 ; If the high byte of the block size is zero, then jump BEQ cmem8 ; to cmem8 to clear a block of fewer than 256 bytes ; If we get here then the high byte of the block size is ; non-zero, so the block we need to clear consists of ; one or more page-sized blocks (i.e. 256-byte blocks), ; as well as one block with fewer than 256 bytes ; ; We now concentrate on clearing the page-sized blocks, ; leaving the block with fewer than 256 bytes for the ; next VBlank ; First we consider whether we can clear a block of 256 ; bytes SUBTRACT_CYCLES 2105 ; Subtract 2105 from the cycle count BMI cmem1 ; If the result is negative, jump to cmem1 to consider ; clearing a 32-byte block in this VBlank, as we don't ; have enough cycles for a 256-byte block JMP cmem2 ; The result is positive, so we have enough cycles to ; clear a 256-byte block in this VBlank, so jump to ; cmem2 to do just that .cmem1 ADD_CYCLES 2059 ; Add 2059 to the cycle count JMP cmem3 ; Jump to cmem3 to consider clearing the block with ; fewer than 256 bytes .cmem2 LDA #0 ; Set A = 0 so the call to FillMemory zeroes the memory ; block LDY #0 ; Set an index in Y to pass to FillMemory, so we start ; clearing memory from clearAddress(1 0) onwards JSR FillMemory ; Call FillMemory to clear a whole 256-byte block of ; memory at clearAddress(1 0) DEC clearBlockSize+1 ; Decrement the high byte of clearBlockSize(1 0), which ; is the same as subtracting 256, as we just cleared 256 ; bytes of memory INC clearAddress+1 ; Increment the high byte of clearAddress(1 0) to point ; at the next 256-byte block of memory after the block ; we just cleared, so we clear that next JMP ClearMemory ; Jump back to ClearMemory to consider clearing the next ; 256 bytes of memory .cmem3 ; If we get here then we did not have enough cycles to ; send a 256-byte block ; Now we consider whether we can clear a block of 32 ; bytes SUBTRACT_CYCLES 318 ; Subtract 318 from the cycle count BMI cmem4 ; If the result is negative, jump to cmem4 to skip ; clearing the next 32-byte block in this VBlank, as we ; have run out of cycles (we will pick up where we left ; off in the next VBlank) JMP cmem5 ; The result is positive, so we have enough cycles to ; clear the next 32-byte block in this VBlank, so jump ; to cmem5 to do just that .cmem4 ADD_CYCLES 277 ; Add 277 to the cycle count JMP cmem7 ; Jump to cmem7 to return from the subroutine .cmem5 LDA #0 ; Set A = 0 so the call to FillMemory zeroes the memory ; block LDY #0 ; Set an index in Y to pass to FillMemory, so we start ; clearing memory from clearAddress(1 0) onwards JSR FillMemory32Bytes ; Call FillMemory to clear 32 bytes of memory from ; clearAddress(1 0) to clearAddress(1 0) + 31 LDA clearAddress ; Set clearAddress(1 0) = clearAddress(1 0) + 32 CLC ; ADC #32 ; So it points at the next memory location to clear STA clearAddress ; after the block we just cleared LDA clearAddress+1 ADC #0 STA clearAddress+1 JMP cmem3 ; Jump back to cmem3 to consider clearing the next 32 ; bytes of memory, which we can keep doing until we run ; out of cycles because we only get here if we don't ; have enough cycles for a 256-byte block, so the cycles ; will run out before we manage to clear eight blocks of ; 32 bytes .cmem6 ADD_CYCLES_CLC 132 ; Add 132 to the cycle count .cmem7 RTS ; Return from the subroutine .cmem8 ; If we get here then we need to clear a block of fewer ; than 256 bytes SUBTRACT_CYCLES 186 ; Subtract 186 from the cycle count BMI cmem9 ; If the result is negative, jump to cmem9 to skip ; clearing the block in this VBlank, as we have run out ; of cycles (we will pick up where we left off in the ; next VBlank) JMP cmem10 ; The result is positive, so we have enough cycles to ; clear the block in this VBlank, so jump to cmem10 ; to do just that .cmem9 ADD_CYCLES 138 ; Add 138 to the cycle count JMP cmem7 ; Jump to cmem7 to return from the subroutine .cmem10 LDA clearBlockSize ; Set A to the size of the block we need to clear, which ; is in the low byte of clearBlockSize(1 0) (as we only ; get here when the high byte of clearBlockSize(1 0) is ; zero) BEQ cmem6 ; If the block size is zero, then there are no bytes to ; clear, so jump to cmem6 to return from the subroutine LSR A ; Set A = clearBlockSize / 16 LSR A LSR A LSR A CMP cycleCount+1 ; If A >= high byte of cycleCount(1 0), then: BCS cmem12 ; ; clearBlockSize / 16 >= cycleCount(1 0) / 256 ; ; so: ; ; clearBlockSize >= cycleCount(1 0) / 16 ; ; If clearing each byte takes up to 16 cycles, then this ; means we can't clear the whole block in this VBlank, ; as we don't have enough cycles, so jump to cmem12 to ; consider clearing it in blocks of 32 bytes rather than ; all at once ; ; (I don't know why this calculation counts 16 cycles ; per byte, as it only takes 8 cycles for FILL_MEMORY ; to clear a byte; perhaps it's an overestimation to be ; safe and cater for all this extra logic code?) ; If we get here then we can clear the block of memory ; in one go ; First we subtract the number of cycles that we need to ; clear the memory block from the cycle count ; ; Each call to the FILL_MEMORY macro takes 8 cycles (6 ; for the STA (clearAddress),Y instruction and 2 for the ; INY instruction), so the total number of cycles we ; will take will be clearBlockSize(1 0) * 8, so that's ; what we subtract from the cycle count LDA #0 ; Set the high byte of clearBlockSize(1 0) = 0 (though STA clearBlockSize+1 ; this should already be the case) LDA clearBlockSize ; Set (A clearBlockSize+1) = clearBlockSize(1 0) ASL A ; Set (A clearBlockSize+1) = (A clearBlockSize+1) * 8 ROL clearBlockSize+1 ; = clearBlockSize(1 0) * 8 ASL A ROL clearBlockSize+1 ASL A ROL clearBlockSize+1 EOR #$FF ; Set cycleCount(1 0) = cycleCount(1 0) SEC ; + ~(A clearBlockSize+1) + 1 ADC cycleCount ; STA cycleCount ; = cycleCount(1 0) - (A clearBlockSize+1) LDA clearBlockSize+1 ; = cycleCount(1 0) - clearBlockSize(1 0) * 8 EOR #$FF ADC cycleCount+1 STA cycleCount+1 ; Next we calculate the entry point into the FillMemory ; routine that will fill clearBlockSize(1 0) bytes of ; memory ; ; FillMemory consists of 256 sequential FILL_MEMORY ; macros, each of which fills one byte, as follows: ; ; STA (clearAddress),Y ; INY ; ; The first instruction takes up two bytes while the INY ; takes up one, so each byte that FillMemory fills takes ; up three bytes of instruction memory ; ; The FillMemory routine ends with an RTS, and is ; followed by the ClearMemory routine, so we can work ; out the entry point for filling clearBlockSize bytes ; as follows: ; ; ClearMemory - 1 - (3 * clearBlockSize) ; ; The 1 is for the RTS, and each of the byte fills has ; three instructions ; ; So this is what we calculate next LDY #0 ; Set an index in Y to pass to FillMemory (which we call ; via the JMP (clearBlockSize) instruction below, so we ; start clearing memory from clearAddress(1 0) onwards STY clearBlockSize+1 ; Set the high byte of clearBlockSize(1 0) = 0 LDA clearBlockSize ; Store the size of the memory block that we want to PHA ; clear on the stack, so we can retrieve it below ASL A ; Set clearBlockSize(1 0) ROL clearBlockSize+1 ; = clearBlockSize(1 0) * 2 + clearBlockSize(1 0) ADC clearBlockSize ; = clearBlockSize(1 0) * 3 STA clearBlockSize ; LDA clearBlockSize+1 ; So clearBlockSize(1 0) contains the block size * 3 ADC #0 STA clearBlockSize+1 ; At this point the C flag is clear, as the high byte ; addition will never overflow, so this means the SBC ; in the following will subtract an extra 1 LDA #LO(ClearMemory) ; Set clearBlockSize(1 0) SBC clearBlockSize ; = ClearMemory - clearBlockSize(1 0) - 1 STA clearBlockSize ; = ClearMemory - (block size * 3) - 1 LDA #HI(ClearMemory) ; SBC clearBlockSize+1 ; So clearBlockSize(1 0) is the address of the entry STA clearBlockSize+1 ; point in FillMemory that fills clearBlockSize(1 0) ; bytes with zero, and we can now call it with this ; instruction: ; ; JMP (clearBlockSize) ; ; So calling cmem11 below will fill memory with the ; value of A, for clearBlockSize(1 0) bytes from ; clearAddress(1 0) + Y onwards ; ; We already set Y to 0 above, so it will start filling ; from clearAddress(1 0) onwards LDA #0 ; Set A = 0 so the call to FillMemory via the ; JMP (clearBlockSize) instruction zeroes the memory ; block JSR cmem11 ; Jump to cmem11 to call the correct entry point in ; FillMemory to clear the memory block, returning here ; when it's done PLA ; Set A to the size of the memory block that we want to ; clear, which we stored on the stack above CLC ; Set clearAddress(1 0) = clearAddress(1 0) + A ADC clearAddress ; STA clearAddress ; So it points at the next memory location to clear LDA clearAddress+1 ; after the block we just cleared ADC #0 STA clearAddress+1 RTS ; Return from the subroutine .cmem11 JMP (clearBlockSize) ; We set up clearBlockSize(1 0) to point to the entry ; point in FillMemory that will fill the correct number ; of bytes with zero, so this clears our memory block ; and returns to the PLA above using a tail call .cmem12 ; If we get here then we need to consider clearing the ; memory in blocks of 32 bytes rather than all at once ADD_CYCLES_CLC 118 ; Add 118 to the cycle count .cmem13 SUBTRACT_CYCLES 321 ; Subtract 321 from the cycle count BMI cmem14 ; If the result is negative, jump to cmem14 to skip ; clearing the block in this VBlank, as we have run out ; of cycles (we will pick up where we left off in the ; next VBlank) JMP cmem15 ; The result is positive, so we have enough cycles to ; clear the block in this VBlank, so jump to cmem15 ; to do just that .cmem14 ADD_CYCLES 280 ; Add 280 to the cycle count JMP cmem16 ; Jump to cmem16 to return from the subroutine .cmem15 LDA clearBlockSize ; Set A = clearBlockSize - 32 SEC SBC #32 BCC cmem17 ; If the subtraction underflowed, then we need to clear ; fewer than 32 bytes (as clearBlockSize < 32), so jump ; to cmem17 to do just that STA clearBlockSize ; Set clearBlockSize - 32 = A ; = clearBlockSize - 32 ; ; So clearBlockSize(1 0) is updated with the new block ; size, as we are about to clear 32 bytes LDA #0 ; Set A = 0 so the call to FillMemory32Bytes zeroes the ; memory block LDY #0 ; Set an index in Y to pass to FillMemory32Bytes, so we ; start clearing memory from clearAddress(1 0) onwards JSR FillMemory32Bytes ; Call FillMemory32Bytes to clear a 32-byte block of ; memory at clearAddress(1 0) LDA clearAddress ; Set clearAddress(1 0) = clearAddress(1 0) + 32 CLC ; ADC #32 ; So it points at the next memory location to clear STA clearAddress ; after the block we just cleared BCC cmem13 INC clearAddress+1 JMP cmem13 ; Jump back to cmem13 to consider clearing the next 32 ; bytes of memory .cmem16 RTS ; Return from the subroutine .cmem17 ; If we get here then we need to clear fewer than 32 ; bytes of memory ADD_CYCLES_CLC 269 ; Add 269 to the cycle count .cmem18 SUBTRACT_CYCLES 119 ; Subtract 119 from the cycle count BMI cmem19 ; If the result is negative, jump to cmem19 to skip ; clearing the block in this VBlank, as we have run out ; of cycles (we will pick up where we left off in the ; next VBlank) JMP cmem20 ; The result is positive, so we have enough cycles to ; clear the block in this VBlank, so jump to cmem20 ; to do just that .cmem19 ADD_CYCLES 78 ; Add 78 to the cycle count JMP cmem16 ; Jump to cmem16 to return from the subroutine .cmem20 LDA clearBlockSize ; Set A = clearBlockSize - 8 SEC SBC #8 BCC cmem22 ; If the subtraction underflowed, then we need to clear ; fewer than 8 bytes (as clearBlockSize < 8), so jump ; to cmem22 to return from the subroutine, as this means ; we have filled the whole block (as we only clear ; memory blocks in multiples of 8 bytes) STA clearBlockSize ; Set clearBlockSize - 8 = A ; = clearBlockSize - 8 ; ; So clearBlockSize(1 0) is updated with the new block ; size, as we are about to clear 8 bytes LDA #0 ; Set A = 0 so the FILL_MEMORY macro zeroes the memory ; block LDY #0 ; Set an index in Y to pass to the FILL_MEMORY macro, so ; we start clearing memory from clearAddress(1 0) ; onwards FILL_MEMORY 8 ; Fill eight bytes at clearAddress(1 0) + Y with A, so ; this zeroes eight bytes at clearAddress(1 0) and ; increments the index counter in Y LDA clearAddress ; Set clearAddress(1 0) = clearAddress(1 0) + 8 CLC ; ADC #8 ; So it points at the next memory location to clear STA clearAddress ; after the block we just cleared BCC cmem21 INC clearAddress+1 .cmem21 JMP cmem18 ; Jump back to cmem18 to consider clearing the next 8 ; bytes of memory .cmem22 ADD_CYCLES_CLC 66 ; Add 66 to the cycle count RTS ; Return from the subroutine
Name: WaitForPPUToFinish [Show more] Type: Subroutine Category: PPU Summary: Wait until the NMI handler has finished updating both bitplanes, so the screen is no longer refreshing
.WaitForPPUToFinish 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 ; Keep looping back to the start of the routine until AND #%01000000 ; bit 6 of the bitplane flags for bitplane 0 is clear BNE WaitForPPUToFinish LDA bitplaneFlags+1 ; Do the same for bitplane 1 AND #%01000000 BNE WaitForPPUToFinish ; We get here when both bitplanes have bit 6 clear, ; which means neither bitplane is configured to send ; nametable data to the PPU ; ; This means the screen has finished refreshing and ; there is no longer any nametable data that needs ; sending to the PPU, so we can return from the ; subroutine RTS ; Return from the subroutine
Name: FlipDrawingPlane [Show more] Type: Subroutine Category: Drawing the screen Summary: Flip the drawing bitplane
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH calls FlipDrawingPlane * DrawScrollFrames calls FlipDrawingPlane * DrawShipInBitplane calls FlipDrawingPlane * LAUN calls FlipDrawingPlane * LL164 calls FlipDrawingPlane * Main flight loop (Part 1 of 16) calls FlipDrawingPlane * RunDemoFlightLoop calls FlipDrawingPlane
.FlipDrawingPlane LDA drawingBitplane ; Set X to the opposite bitplane to the current drawing EOR #1 ; bitplane TAX JSR SetDrawingBitplane ; Set X as the new drawing bitplane, so this effectively ; flips the drawing bitplane between 0 and 1 JMP ClearDrawingPlane ; Jump to ClearDrawingPlane to clear the buffers for the ; new drawing bitplane, returning from the subroutine ; using a tail call
Name: SetDrawingBitplane [Show more] Type: Subroutine Category: Drawing the screen Summary: Set the drawing bitplane to a specified value
Context: See this subroutine on its own page References: This subroutine is called as follows: * FlipDrawingPlane calls SetDrawingBitplane * SendViewToPPU calls SetDrawingBitplane * SetDrawingPlaneTo0 calls SetDrawingBitplane * SetupViewInNMI calls SetDrawingBitplane * TT66 calls SetDrawingBitplane

Arguments: X The new value of the drawing bitplane
.SetDrawingBitplane STX drawingBitplane ; Set the drawing bitplane to X LDA lastPattern,X ; Set the next free pattern number in firstFreePattern STA firstFreePattern ; to the number of the last pattern that was sent to the ; PPU for the new bitplane LDA nameBufferHiAddr,X ; Set the high byte of the nametable buffer for the new STA nameBufferHi ; bitplane in nameBufferHiAddr LDA #0 ; Set the low byte of pattBufferAddr(1 0) to zero (we STA pattBufferAddr ; will set the high byte in SetPatternBuffer below STA drawingPlaneDebug ; Set drawingPlaneDebug = 0 (though this value is never ; read, so this has no effect) ; Fall through into SetPatternBuffer to set the high ; bytes of the patten buffer address variables
Name: SetPatternBuffer [Show more] Type: Subroutine Category: Drawing the screen Summary: Set the high byte of the pattern buffer address variables
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT128 calls SetPatternBuffer

Arguments: X The bitplane whose pattern address we should use
.SetPatternBuffer LDA pattBufferHiAddr,X ; Set the high byte of pattBufferAddr(1 0) to the STA pattBufferAddr+1 ; correct address for the pattern buffer for bitplane X LSR A ; Set pattBufferHiDiv8 to the high byte of the pattern LSR A ; buffer address, divided by 8 LSR A STA pattBufferHiDiv8 RTS ; Return from the subroutine
Name: CopySmallBlock [Show more] Type: Subroutine Category: Utility routines Summary: An unused routine that copies a small number of pages in memory
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

Arguments: V(1 0) Source address SC(1 0) Destination address X Number of pages of memory to copy
.CopySmallBlock LDY #0 ; Set an index counter in Y .cops1 LDA (V),Y ; Copy the Y-th byte from V(1 0) to SC(1 0) STA (SC),Y DEY ; Decrement the index counter BNE cops1 ; Loop back until we have copied a whole page of bytes INC V+1 ; Increment the high bytes of V(1 0) and SC(1 0) to INC SC+1 ; point to the next page in memory DEX ; Decrement the page counter BNE cops1 ; Loop back until we have copied X pages of memory RTS ; Return from the subroutine
Name: CopyLargeBlock [Show more] Type: Subroutine Category: Utility routines Summary: An unused routine that copies a large number of pages in memory
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

Arguments: SC2(1 0) Source address SC(1 0) Destination address V The number of pages top copy in each set V+1 The number of sets, so we copy V * V+1 pages X Number of pages of memory to copy
.CopyLargeBlock LDY #0 ; Set an index counter in Y INC V ; Increment the page counter in V so we can use a BNE ; below to copy V pages INC V+1 ; Increment the page counter in V+1 so we can use a BNE ; below to copy V+1 sets of V pages .copl1 LDA (SC2),Y ; Copy the Y-th byte from SC2(1 0) to SC(1 0) STA (SC),Y INY ; Increment the index counter BNE copl2 ; If we haven't reached the end of the page, jump to ; copl2 to skip the following INC SC+1 ; Increment the high bytes of SC(1 0) and SC2(1 0) to INC SC2+1 ; point to the next page in memory .copl2 DEC V ; Loop back to repeat the above until we have copied V BNE copl1 ; pages DEC V+1 ; Loop back to repeat the above until we have copied V+1 BNE copl1 ; sets of V pages RTS ; Return from the subroutine
Name: WaitFor3xVBlank [Show more] Type: Subroutine Category: Utility routines Summary: Wait for three VBlanks to pass
Context: See this subroutine on its own page References: This subroutine is called as follows: * ResetScreen calls WaitFor3xVBlank
.WaitFor3xVBlank 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 .wait1 LDA PPU_STATUS ; Wait for the first VBlank to pass, which will set bit BPL wait1 ; 7 of PPU_STATUS (and reading PPU_STATUS clears bit 7, ; ready for the next VBlank) .wait2 LDA PPU_STATUS ; Wait for the second VBlank to pass BPL wait2 ; Fall through into WaitForVBlank to wait for the third ; VBlank before returning from the subroutine
Name: WaitForVBlank [Show more] Type: Subroutine Category: Utility routines Summary: Wait for the next VBlank to pass
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeSoundsAtVBlank calls WaitForVBlank
.WaitForVBlank LDA PPU_STATUS ; Wait for the next VBlank to pass BPL WaitForVBlank RTS ; Return from the subroutine
Name: MakeSoundsAtVBlank [Show more] Type: Subroutine Category: Sound Summary: Wait for the next VBlank and make the current sounds (music and sound effects)
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendDataNowToPPU calls MakeSoundsAtVBlank * SendViewToPPU calls MakeSoundsAtVBlank

Returns: X X is preserved
.MakeSoundsAtVBlank TXA ; Store X on the stack, so we can retrieve it below PHA JSR WaitForVBlank ; Wait for the next VBlank to pass JSR MakeSounds_b6 ; Call the MakeSounds routine to make the current sounds ; (music and sound effects) PLA ; Restore X from the stack so it is preserved TAX RTS ; Return from the subroutine
Name: DrawMessageInNMI [Show more] Type: Subroutine Category: Drawing the screen Summary: Configure the NMI to send the portion of the screen that contains the in-flight message to the PPU (i.e. tile rows 22 to 24)
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChangeCmdrName calls DrawMessageInNMI * ChangeLetter calls DrawMessageInNMI * FlightLoop4To16 calls DrawMessageInNMI * InputName calls DrawMessageInNMI * PrintFlightMessage calls DrawMessageInNMI * SetSelectedSystem calls DrawMessageInNMI * YESNO calls DrawMessageInNMI
.DrawMessageInNMI JSR WaitForPPUToFinish ; Wait until both bitplanes of the screen have been ; sent to the PPU, so the screen is fully updated and ; there is no more data waiting to be sent to the PPU LDA firstFreePattern ; Tell the NMI handler to send pattern entries up to the STA lastPattern ; first free pattern, for both bitplanes STA lastPattern+1 LDA #88 ; Tell the NMI handler to send nametable entries from STA firstNameTile ; tile 88 * 8 = 704 onwards (i.e. from the start of tile ; row 22) LDA #100 ; Tell the NMI handler to send nametable entries up to STA lastNameTile ; tile 100 * 8 = 800 (i.e. up to the end of tile row 24) STA lastNameTile+1 ; in both bitplanes LDA #%11000100 ; Set both bitplane flags as follows: STA bitplaneFlags ; STA bitplaneFlags+1 ; * Bit 2 set = send tiles up to end of the buffer ; * Bit 3 clear = don't clear buffers after sending ; * Bit 4 clear = we've not started sending data yet ; * Bit 5 clear = we have not yet sent all the data ; * Bit 6 set = send both pattern and nametable data ; * Bit 7 set = send data to the PPU ; ; Bits 0 and 1 are ignored and are always clear ; ; The NMI handler will now start sending data to the PPU ; according to the above configuration, splitting the ; process across multiple VBlanks if necessary JMP WaitForPPUToFinish ; Wait until both bitplanes of the screen have been ; sent to the PPU, so the screen is fully updated and ; there is no more data waiting to be sent to the PPU, ; and return from the subroutine using a tail call
Name: DrawShipInBitplane [Show more] Type: Subroutine Category: Drawing ships Summary: Flip the drawing bitplane and draw the current ship in the newly flipped bitplane
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIEF calls DrawShipInBitplane * ESCAPE calls DrawShipInBitplane * PAS1 calls DrawShipInBitplane * TITLE calls DrawShipInBitplane
.DrawShipInBitplane JSR FlipDrawingPlane ; Flip the drawing bitplane so we draw into the bitplane ; that isn't visible on-screen JSR LL9_b1 ; Draw the current ship into the newly flipped drawing ; bitplane ; Fall through into DrawBitplaneInNMI to configure the ; NMI to send the drawing bitplane to the PPU
Name: DrawBitplaneInNMI [Show more] Type: Subroutine Category: Drawing the screen Summary: Configure the NMI to send the drawing bitplane to the PPU after drawing the box edges and setting the next free tile number Deep dive: Views and view types in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawScrollFrames calls DrawBitplaneInNMI * DrawSpaceViewInNMI calls DrawBitplaneInNMI * LAUN calls DrawBitplaneInNMI * LL164 calls DrawBitplaneInNMI * RunDemoFlightLoop calls DrawBitplaneInNMI
.DrawBitplaneInNMI LDA #%11001000 ; Set A so we set the drawing bitplane flags in ; SetDrawPlaneFlags as follows: ; ; * Bit 2 clear = send tiles up to configured numbers ; * Bit 3 set = clear buffers after sending data ; * Bit 4 clear = we've not started sending data yet ; * Bit 5 clear = we have not yet sent all the data ; * Bit 6 set = send both pattern and nametable data ; * Bit 7 set = send data to the PPU ; ; Bits 0 and 1 are ignored and are always clear ; ; This configures the NMI to send nametable and pattern ; data for the drawing bitplane to the PPU during VBlank ; Fall through into SetDrawPlaneFlags to set the ; bitplane flags, draw the box edges and set the next ; free tile number
Name: SetDrawPlaneFlags [Show more] Type: Subroutine Category: Drawing the screen Summary: Set the drawing bitplane flags to the specified value, draw the box edges and set the next free tile number Deep dive: Drawing vector graphics using NES tiles
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH calls SetDrawPlaneFlags * DrawSpaceViewInNMI calls SetDrawPlaneFlags * SendBitplaneToPPU calls SetDrawPlaneFlags * SetupViewInNMI calls SetDrawPlaneFlags
.SetDrawPlaneFlags PHA ; Store A on the stack, so we can retrieve them below ; when setting the new drawing bitplane flags JSR DrawBoxEdges ; Draw the left and right edges of the box along the ; sides of the screen, drawing into the nametable buffer ; for the drawing bitplane LDX drawingBitplane ; Set X to the drawing bitplane LDA firstFreePattern ; Tell the NMI handler to send pattern entries up to the STA lastPattern,X ; first free pattern, for the drawing bitplane in X PLA ; Retrieve A from the stack and set it as the value of STA bitplaneFlags,X ; the drawing bitplane flags RTS ; Return from the subroutine
Name: SendInventoryToPPU [Show more] Type: Subroutine Category: PPU Summary: Send X batches of 16 bytes from SC(1 0) to the PPU, for sending the inventory icon bar image
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendViewToPPU calls SendInventoryToPPU

Arguments: X The number of batches of 16 bytes to send to the PPU SC(1 0) The address of the data to send
.SendInventoryToPPU LDY #0 ; Set Y as an index counter for the following block, ; which sends 16 bytes of data from SC(1 0) to the PPU, ; using Y as an index that starts at 0 and increments ; after each byte ; ; We repeat this process for X iterations ; We repeat the following code 16 times, so it sends ; one whole pattern of 16 bytes to the PPU (eight bytes ; for each bitplane) LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA (SC),Y ; Send the Y-th byte of SC(1 0) to the PPU and increment STA PPU_DATA ; the index in Y INY LDA SC ; Set SC(1 0) = SC(1 0) + 16 CLC ; ADC #16 ; Starting with the low bytes STA SC BCC smis1 ; And then the high bytes INC SC+1 .smis1 DEX ; Decrement the block counter in X BNE SendInventoryToPPU ; Loop back to the start of the subroutine until we have ; sent X batches of 16 bytes RTS ; Return from the subroutine
Name: TWOS [Show more] Type: Variable Category: Drawing pixels Summary: Ready-made single-pixel character row bytes for the space view Deep dive: Drawing pixels in the NES version
Context: See this variable on its own page References: This variable is used as follows: * DrawHangarWallLine uses TWOS * DrawVerticalLine (Part 2 of 3) uses TWOS * DrawVerticalLine (Part 3 of 3) uses TWOS * LOIN (Part 3 of 7) uses TWOS * LOIN (Part 4 of 7) uses TWOS * LOIN (Part 5 of 7) uses TWOS * PIXEL uses TWOS

Ready-made bytes for plotting one-pixel points the space view. See the PIXEL routine for details.
.TWOS EQUB %10000000 EQUB %01000000 EQUB %00100000 EQUB %00010000 EQUB %00001000 EQUB %00000100 EQUB %00000010 EQUB %00000001 EQUB %10000000 EQUB %01000000
Name: TWOS2 [Show more] Type: Variable Category: Drawing pixels Summary: Ready-made double-pixel character row bytes for the space view Deep dive: Drawing pixels in the NES version
Context: See this variable on its own page References: This variable is used as follows: * DrawDash uses TWOS2

Ready-made bytes for plotting two-pixel points the space view. See the PIXEL routine for details.
.TWOS2 EQUB %11000000 EQUB %11000000 EQUB %01100000 EQUB %00110000 EQUB %00011000 EQUB %00001100 EQUB %00000110 EQUB %00000011
Name: TWFL [Show more] Type: Variable Category: Drawing lines Summary: Ready-made character rows for the left end of a horizontal line in the space view
Context: See this variable on its own page References: This variable is used as follows: * HLOIN (Part 4 of 5) uses TWFL * HLOIN (Part 5 of 5) uses TWFL

Ready-made bytes for plotting horizontal line end caps in the space view. This table provides a byte with pixels at the left end, which is used for the right end of the line. See the HLOIN routine for details.
.TWFL EQUB %10000000 EQUB %11000000 EQUB %11100000 EQUB %11110000 EQUB %11111000 EQUB %11111100 EQUB %11111110
Name: TWFR [Show more] Type: Variable Category: Drawing lines Summary: Ready-made character rows for the right end of a horizontal line in the space view
Context: See this variable on its own page References: This variable is used as follows: * HLOIN (Part 2 of 5) uses TWFR * HLOIN (Part 5 of 5) uses TWFR

Ready-made bytes for plotting horizontal line end caps in the space view. This table provides a byte with pixels at the right end, which is used for the left end of the line. See the HLOIN routine for details.
.TWFR EQUB %11111111 EQUB %01111111 EQUB %00111111 EQUB %00011111 EQUB %00001111 EQUB %00000111 EQUB %00000011 EQUB %00000001
Name: yLookupLo [Show more] Type: Variable Category: Drawing pixels Summary: Lookup table for converting pixel y-coordinate to tile number (low byte)
The NES screen mode is made up of 8x8-pixel tiles, with 32 tiles (256 pixels) across the screen, and either 30 tiles (240 pixels) or 28 tiles (224 pixels) vertically, for PAL or NTSC. This lookup table converts a pixel y-coordinate into the number of the first tile on the row containing the pixel. Pixel coordinate (0, 0) is mapped to the top-left pixel of the third row of tiles in the nametable, and the first column of tiles is at column 1 rather than 0 (as the screen is scrolled horizontally by 8 pixels via PPU_SCROLL), so pixel y-coordinates 0 to 7 are mapped to tile 65 (i.e. 2 * 32 + 1), pixel y-coordinates 8 to 15 are mapped to tile 97 (i.e. 3 * 32 + 1), and so on.
.yLookupLo FOR I%, 16, 239 EQUB LO((I% DIV 8) * 32 + 1) NEXT
Name: yLookupHi [Show more] Type: Variable Category: Drawing pixels Summary: Lookup table for converting pixel y-coordinate to tile number (high byte)
The NES screen mode is made up of 8x8-pixel tiles, with 32 tiles (256 pixels) across the screen, and either 30 tiles (240 pixels) or 28 tiles (224 pixels) vertically, for PAL or NTSC. This lookup table converts a pixel y-coordinate into the number of the first tile on the row containing the pixel. Pixel coordinate (0, 0) is mapped to the top-left pixel of the third row of tiles in the nametable, and the first column of tiles is at column 1 rather than 0 (as the screen is scrolled horizontally by 8 pixels via PPU_SCROLL), so pixel y-coordinates 0 to 7 are mapped to tile 65 (i.e. 2 * 32 + 1), pixel y-coordinates 8 to 15 are mapped to tile 97 (i.e. 3 * 32 + 1), and so on.
.yLookupHi FOR I%, 16, 239 EQUB HI((I% DIV 8) * 32 + 1) NEXT
Name: GetRowNameAddress [Show more] Type: Subroutine Category: Drawing the screen Summary: Get the addresses in the nametable buffers for the start of a given character row
This routine returns the index of the start of a text row in the nametable buffers. Character row 0 (i.e. YC = 0) is mapped to the second row on-screen, as the first row is taken up by the box edge. It's also worth noting that the first column in the nametable is column 1, not column 0, as the screen has a horizontal scroll of 8, so the leftmost tile 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.
Arguments: YC The text row
Returns: SC(1 0) The address in nametable buffer 0 for the start of the row SC2(1 0) The address in nametable buffer 1 for the start of the row
.GetRowNameAddress LDA #0 ; Set SC+1 = 0, for use at the top byte of SC(1 0) in STA SC+1 ; the calculation below LDA YC ; If YC = 0, then we need to return the address of the BEQ grow1 ; start of the top character row (i.e. the second row ; on-screen), so jump to grow1 LDA YC ; Set A = YC + 1 CLC ; ADC #1 ; So this is the nametable row number for text row YC, ; as nametable row 0 is taken up by the top box edge ASL A ; Set SC(1 0) = (SC+1 A) << 5 + 1 ASL A ; = (0 A) << 5 + 1 ASL A ; = (YC + 1) * 32 + 1 ASL A ; ROL SC+1 ; This sets SC(1 0) to the offset within the nametable SEC ; of the start of the relevant row, as there are 32 ROL A ; tiles on each row ROL SC+1 ; STA SC ; The YC + 1 part skips the top on-screen row to start ; just below the top box edge, and the final + 1 takes ; care of the horizontal scrolling, which makes the ; first column number 1 rather than 0 ; ; The final ROL SC+1 also clears the C flag, as we know ; bits 1 to 7 of SC+1 were clear before the rotation STA SC2 ; Set the low byte of SC2(1 0) to the low byte of ; SC(1 0), as the addresses of the two nametable buffers ; only differ in the high bytes LDA SC+1 ; Set SC(1 0) = SC(1 0) + nameBuffer0 ADC #HI(nameBuffer0) ; STA SC+1 ; So SC(1 0) now points to the row's address in ; nametable buffer 0 (this addition works because we ; know that the C flag is clear and the low byte of ; nameBuffer0 is zero) ; ; This addition will never overflow, as we know SC+1 is ; in the range 0 to 3, so this also clears the C flag ; Each nametable buffer is 1024 bytes in size, which is ; four pages of 256 bytes, and nametable buffer 1 is ; straight after nametable buffer 0 in memory, so we can ; calculate the row's address in nametable buffer 1 in ; SC2(1 0) by simply adding 4 to the high byte ADC #4 ; Set SC2(1 0) = SC(1 0) + (4 0) STA SC2+1 ; ; So SC2(1 0) now points to the row's address in ; nametable buffer 1 (this addition works because we ; know that the C flag is clear RTS ; Return from the subroutine .grow1 ; If we get here then we want to return the address of ; the top character row (as YC = ), which is actually ; the second on-screen row (row 1), as the first row is ; taken up by the top of the box LDA #HI(nameBuffer0+1*32+1) ; Set SC(1 0) to the address of the tile in STA SC+1 ; column 1 on tile row 1 in nametable buffer 0 LDA #LO(nameBuffer0+1*32+1) STA SC LDA #HI(nameBuffer1+1*32+1) ; Set SC(1 0) to the address of the tile in STA SC2+1 ; column 1 on tile row 1 in nametable buffer 1 LDA #LO(nameBuffer1+1*32+1) STA SC2 RTS ; Return from the subroutine
Name: LOIN (Part 1 of 7) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a line: Calculate the line gradient in the form of deltas Deep dive: Bresenham's line algorithm Drawing lines in the NES version
Context: See this subroutine on its own page References: This subroutine is called as follows: * BLINE calls LOIN * CLIP_b1 calls LOIN * DrawLightning calls LOIN * LASLI calls LOIN * LL9 (Part 9 of 12) calls LOIN * LL9 (Part 10 of 12) calls LOIN * SHPPT calls LOIN * TT15 calls LOIN

This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. This stage calculates the line deltas.
Arguments: X1 The screen x-coordinate of the start of the line Y1 The screen y-coordinate of the start of the line X2 The screen x-coordinate of the end of the line Y2 The screen y-coordinate of the end of the line
.LOIN STY YSAV ; Store Y into YSAV, so we can preserve it across the ; call to this subroutine 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 #128 ; Set S = 128, which is the starting point for the STA S ; slope error (representing half a pixel) ASL A ; Set SWAP = 0, as %10000000 << 1 = 0 STA SWAP LDA X2 ; Set A = X2 - X1 SBC X1 ; = delta_x ; ; This subtraction works as the ASL A above sets the C ; flag BCS LI1 ; If X2 > X1 then A is already positive and we can skip ; the next three instructions EOR #%11111111 ; Negate the result in A by flipping all the bits and ADC #1 ; adding 1, i.e. using two's complement to make it ; positive .LI1 STA P ; Store A in P, so P = |X2 - X1|, or |delta_x| SEC ; Set the C flag, ready for the subtraction below LDA Y2 ; Set A = Y2 - Y1 SBC Y1 ; = delta_y ; ; This subtraction works as we either set the C flag ; above, or we skipped that SEC instruction with a BCS BCS LI2 ; If Y2 > Y1 then A is already positive and we can skip ; the next two instructions EOR #%11111111 ; Negate the result in A by flipping all the bits and ADC #1 ; adding 1, i.e. using two's complement to make it ; positive .LI2 STA Q ; Store A in Q, so Q = |Y2 - Y1|, or |delta_y| CMP P ; If Q < P, jump to STPX to step along the x-axis, as BCC STPX ; the line is closer to being horizontal than vertical JMP STPY ; Otherwise Q >= P so jump to STPY to step along the ; y-axis, as the line is closer to being vertical than ; horizontal
Name: LOIN (Part 2 of 7) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a line: Line has a shallow gradient, step right along x-axis Deep dive: Bresenham's line algorithm Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. If we get here, then: * |delta_y| < |delta_x| * The line is closer to being horizontal than vertical * We are going to step right along the x-axis * We potentially swap coordinates to make sure X1 < X2
.STPX LDX X1 ; Set X = X1 CPX X2 ; If X1 < X2, jump down to LI3, as the coordinates are BCC LI3 ; already in the order that we want DEC SWAP ; Otherwise decrement SWAP from 0 to $FF, to denote that ; we are swapping the coordinates around (though note ; that we don't use this value anywhere, as in the ; original versions of Elite it is used to omit the ; first pixel of each line, which we don't have to do ; in the NES version as it doesn't use EOR plotting) LDA X2 ; Swap the values of X1 and X2 STA X1 STX X2 TAX ; Set X = X1 LDA Y2 ; Swap the values of Y1 and Y2 LDY Y1 STA Y1 STY Y2 .LI3 ; By this point we know the line is horizontal-ish and ; X1 < X2, so we're going from left to right as we go ; from X1 to X2 ; The following section calculates: ; ; Q = Q / P ; = |delta_y| / |delta_x| ; ; using the log tables at logL and log to calculate: ; ; A = log(Q) - log(P) ; = log(|delta_y|) - log(|delta_x|) ; ; by first subtracting the low bytes of the logarithms ; from the table at LogL, and then subtracting the high ; bytes from the table at log, before applying the ; antilog to get the result of the division and putting ; it in Q LDX Q ; Set X = |delta_y| BEQ LIlog7 ; If |delta_y| = 0, jump to LIlog7 to return 0 as the ; result of the division LDA logL,X ; Set A = log(Q) - log(P) LDX P ; = log(|delta_y|) - log(|delta_x|) SEC ; SBC logL,X ; by first subtracting the low bytes of log(Q) - log(P) BMI LIlog4 ; If A > 127, jump to LIlog4 LDX Q ; And then subtracting the high bytes of log(Q) - log(P) LDA log,X ; so now A contains the high byte of log(Q) - log(P) LDX P SBC log,X BCS LIlog5 ; If the subtraction fitted into one byte and didn't ; underflow, then log(Q) - log(P) < 256, so we jump to ; LIlog5 to return a result of 255 TAX ; Otherwise we set A to the A-th entry from the antilog LDA antilog,X ; table so the result of the division is now in A JMP LIlog6 ; Jump to LIlog6 to return the result .LIlog5 LDA #255 ; The division is very close to 1, so set A to the BNE LIlog6 ; closest possible answer to 256, i.e. 255, and jump to ; LIlog6 to return the result (this BNE is effectively a ; JMP as A is never zero) .LIlog7 LDA #0 ; The numerator in the division is 0, so set A to 0 and BEQ LIlog6 ; jump to LIlog6 to return the result (this BEQ is ; effectively a JMP as A is always zero) .LIlog4 LDX Q ; Subtract the high bytes of log(Q) - log(P) so now A LDA log,X ; contains the high byte of log(Q) - log(P) LDX P SBC log,X BCS LIlog5 ; If the subtraction fitted into one byte and didn't ; underflow, then log(Q) - log(P) < 256, so we jump to ; LIlog5 to return a result of 255 TAX ; Otherwise we set A to the A-th entry from the LDA antilogODD,X ; antilogODD so the result of the division is now in A .LIlog6 STA Q ; Store the result of the division in Q, so we have: ; ; Q = |delta_y| / |delta_x| LDA P ; Set P = P + 1 CLC ; = |delta_x| + 1 ADC #1 ; STA P ; We will use P as the x-axis counter, and we add 1 to ; ensure we include the pixel at each end LDY Y1 ; If Y1 >= Y2, skip the following instruction CPY Y2 BCS P%+5 JMP DOWN ; Y1 < Y2, so jump to DOWN, as we need to draw the line ; to the right and down
Name: LOIN (Part 3 of 7) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a shallow line going right and up or left and down Deep dive: Bresenham's line algorithm Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. If we get here, then: * The line is going right and up (no swap) or left and down (swap) * X1 < X2 and Y1 > Y2 * Draw from (X1, Y1) at bottom left to (X2, Y2) at top right
LDA X1 ; Set SC2(1 0) = (nameBufferHi 0) + yLookup(Y) + X1 / 8 LSR A ; LSR A ; where yLookup(Y) uses the (yLookupHi yLookupLo) table LSR A ; to convert the pixel y-coordinate in Y into the number CLC ; of the first tile on the row containing the pixel ADC yLookupLo,Y ; STA SC2 ; Adding nameBufferHi and X1 / 8 therefore sets SC2(1 0) LDA nameBufferHi ; to the address of the entry in the nametable buffer ADC yLookupHi,Y ; that contains the tile number for the tile containing STA SC2+1 ; the pixel at (X1, Y), i.e. the line we are drawing TYA ; Set Y = Y mod 8, which is the pixel row within the AND #7 ; character block at which we want to draw the start of TAY ; our line (as each character block has 8 rows) LDA X1 ; Set X = X1 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA TWOS,X ; Fetch a 1-pixel byte from TWOS where pixel X is set .loin1 STA R ; Store the pixel byte in R .loin2 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; If the nametable buffer entry is non-zero for the tile LDA (SC2,X) ; containing the pixel that we want to draw, then a BNE loin3 ; pattern has already been allocated to this entry, so ; skip the following LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ loin7 ; patterns to use for drawing lines and pixels, so jump ; to loin7 to move on to the next pixel in the line STA (SC2,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; pattern to cover the pixel that we want to draw by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be added to ; the nametable the next time we need to draw lines or ; pixels into a pattern .loin3 LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC CLC ; Clear the C flag for the additions below .loin4 ; We now loop along the line from left to right, using P ; as a decreasing counter, and at each count we plot a ; single pixel using the pixel mask in R LDA R ; Fetch the pixel byte from R ORA (SC),Y ; Store R into screen memory at SC(1 0), using OR logic STA (SC),Y ; so it merges with whatever is already on-screen DEC P ; Decrement the x-axis counter in P BEQ loin9 ; If we have just reached the end of the line along the ; x-axis, jump to loin9 to return from the subroutine LDA S ; Set S = S + Q to update the slope error ADC Q STA S BCC loin5 ; If the addition didn't overflow, jump to loin5 to skip ; the following DEY ; Otherwise we just overflowed, so decrement Y to move ; to the pixel line above BMI loin6 ; If Y is negative we need to move up into the character ; block above, so jump to loin6 to decrement the screen ; address accordingly (jumping back to loin1 afterwards) .loin5 LSR R ; Shift the single pixel in R to the right to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BNE loin4 ; If the pixel didn't fall out of the right end of R, ; then the pixel byte is still non-zero, so loop back ; to loin4 LDA #%10000000 ; Set a pixel byte in A with the leftmost pixel set, as ; we need to move to the next character block along INC SC2 ; Increment SC2(1 0) to point to the next tile number to BNE loin1 ; the right in the nametable buffer and jump up to loin1 INC SC2+1 ; to fetch the tile details for the new nametable entry BNE loin1 .loin6 LDA SC2 ; If we get here then we need to move up into the SBC #32 ; character block above, so we subtract 32 from SC2(1 0) STA SC2 ; to get the tile number on the row above (as there are BCS P%+4 ; 32 tiles on each row) DEC SC2+1 LDY #7 ; Set the pixel line in Y to the last line in the new ; character block LSR R ; Shift the single pixel in R to the right to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BNE loin2 ; If the pixel didn't fall out of the right end of R, ; then the pixel byte is still non-zero, so loop back ; to loin2 LDA #%10000000 ; Set a pixel byte in A with the leftmost pixel set, as ; we need to move to the next character block along INC SC2 ; Increment SC2(1 0) to point to the next tile number to BNE loin1 ; the right in the nametable buffer and jump up to loin1 INC SC2+1 ; to fetch the tile details for the new nametable entry BNE loin1 ; (this BNE is effectively a JMP as the high byte of ; SC2(1 0) will never be zero as the nametable buffers ; start at address $7000, so the high byte is always at ; least $70) .loin7 DEC P ; Decrement the x-axis counter in P BEQ loin9 ; If we have just reached the end of the line along the ; x-axis, jump to loin9 to return from the subroutine CLC ; Set S = S + Q to update the slope error LDA S ADC Q STA S BCC loin8 ; If the addition didn't overflow, jump to loin8 to skip ; the following DEY ; Otherwise we just overflowed, so decrement Y to move ; to the pixel line above BMI loin6 ; If Y is negative we need to move up into the character ; block above, so jump to loin6 to move to the previous ; row of nametable entries (jumping back to loin1 ; afterwards) .loin8 LSR R ; Shift the single pixel in R to the right to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BNE loin7 ; If the pixel didn't fall out of the right end of R, ; then the pixel byte is still non-zero, so loop back ; to loin7 LDA #%10000000 ; Set a pixel byte in A with the leftmost pixel set, as ; we need to move to the next character block along INC SC2 ; Increment SC2(1 0) to point to the next tile number to BNE P%+4 ; the right in the nametable buffer and jump up to loin1 INC SC2+1 ; to fetch the tile details for the new nametable entry JMP loin1 .loin9 LDY YSAV ; Restore Y from YSAV, so that it's preserved SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 CLC ; Clear the C flag for the routine to return RTS ; Return from the subroutine
Name: LOIN (Part 4 of 7) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a shallow line going right and down or left and up Deep dive: Bresenham's line algorithm Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. If we get here, then: * The line is going right and down (no swap) or left and up (swap) * X1 < X2 and Y1 <= Y2 * Draw from (X1, Y1) at top left to (X2, Y2) at bottom right
.DOWN LDA X1 ; Set SC2(1 0) = (nameBufferHi 0) + yLookup(Y) + X1 / 8 LSR A ; LSR A ; where yLookup(Y) uses the (yLookupHi yLookupLo) table LSR A ; to convert the pixel y-coordinate in Y into the number CLC ; of the first tile on the row containing the pixel ADC yLookupLo,Y ; STA SC2 ; Adding nameBufferHi and X1 / 8 therefore sets SC2(1 0) LDA nameBufferHi ; to the address of the entry in the nametable buffer ADC yLookupHi,Y ; that contains the tile number for the tile containing STA SC2+1 ; the pixel at (X1, Y), i.e. the line we are drawing TYA ; Set Y = Y mod 8, which is the pixel row within the AND #7 ; character block at which we want to draw the start of TAY ; our line (as each character block has 8 rows) LDA X1 ; Set X = X1 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA TWOS,X ; Fetch a 1-pixel byte from TWOS where pixel X is set .loin10 STA R ; Store the pixel byte in R .loin11 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; If the nametable buffer entry is non-zero for the tile LDA (SC2,X) ; containing the pixel that we want to draw, then a BNE loin12 ; pattern has already been allocated to this entry, so ; skip the following LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ loin16 ; patterns to use for drawing lines and pixels, so jump ; to loin16 to move on to the next pixel in the line STA (SC2,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; pattern to cover the pixel that we want to draw by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be added to ; the nametable the next time we need to draw lines or ; pixels into a pattern .loin12 LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC CLC ; Clear the C flag for the additions below .loin13 ; We now loop along the line from left to right, using P ; as a decreasing counter, and at each count we plot a ; single pixel using the pixel mask in R LDA R ; Fetch the pixel byte from R ORA (SC),Y ; Store R into screen memory at SC(1 0), using OR logic STA (SC),Y ; so it merges with whatever is already on-screen DEC P ; Decrement the x-axis counter in P BEQ loin9 ; If we have just reached the end of the line along the ; x-axis, jump to loin9 to return from the subroutine LDA S ; Set S = S + Q to update the slope error ADC Q STA S BCC loin14 ; If the addition didn't overflow, jump to loin14 to ; skip the following INY ; Otherwise we just overflowed, so increment Y to move ; to the pixel line below CPY #8 ; If Y = 8 then we have just gone past the bottom of the BEQ loin15 ; character block, so jump to loin15 to move to the next ; row of nametable entries (jumping back to loin10 ; afterwards) .loin14 LSR R ; Shift the single pixel in R to the right to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BNE loin13 ; If the pixel didn't fall out of the right end of R, ; then the pixel byte is still non-zero, so loop back ; to loin13 LDA #%10000000 ; Set a pixel byte in A with the leftmost pixel set, as ; we need to move to the next character block along INC SC2 ; Increment SC2(1 0) to point to the next tile number to BNE loin10 ; the right in the nametable buffer and jump up to INC SC2+1 ; loin10 to fetch the tile details for the new nametable JMP loin10 ; entry .loin15 ; If we get here then we have just gone past the bottom ; of the character block ; ; At this point the C flag is set, as we jumped here ; using a BEQ, so the ADC #31 below actually adds 32 LDA SC2 ; If we get here then we need to move down into the ADC #31 ; character block above, so we add 32 to SC2(1 0) STA SC2 ; to get the tile number on the row above (as there are BCC P%+4 ; 32 tiles on each row) INC SC2+1 LDY #0 ; Set the pixel line in Y to the first line in the new ; character block LSR R ; Shift the single pixel in R to the right to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BNE loin11 ; If the pixel didn't fall out of the right end of R, ; then the pixel byte is still non-zero, so loop back ; to loin11 LDA #%10000000 ; Set a pixel byte in A with the leftmost pixel set, as ; we need to move to the next character block along INC SC2 ; Increment SC2(1 0) to point to the next tile number to BNE loin10 ; the right in the nametable buffer and jump up to INC SC2+1 ; loin10 to fetch the tile details for the new nametable JMP loin10 ; entry .loin16 ; If we get here then we have run out of tiles to ; allocate to the line drawing, so we continue with the ; same calculations, but don't actually draw anything in ; this character block DEC P ; Decrement the x-axis counter in P BEQ loin19 ; If we have just reached the end of the line along the ; x-axis, jump to loin19 to return from the subroutine CLC ; Set S = S + Q to update the slope error LDA S ADC Q STA S BCC loin17 ; If the addition didn't overflow, jump to loin17 to ; skip the following INY ; Otherwise we just overflowed, so increment Y to move ; to the pixel line below CPY #8 ; If Y = 8 then we have just gone past the bottom of the BEQ loin15 ; character block, so jump to loin15 to move to the next ; row of nametable entries (jumping back to loin10 ; afterwards) .loin17 LSR R ; Shift the single pixel in R to the right to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BNE loin16 ; If the pixel didn't fall out of the right end of R, ; then the pixel byte is still non-zero, so loop back ; to loin16 LDA #%10000000 ; Set a pixel byte in A with the leftmost pixel set, as ; we need to move to the next character block along INC SC2 ; Increment SC2(1 0) to point to the next tile number to BNE P%+4 ; the right in the nametable buffer and jump up to INC SC2+1 ; loin10 to fetch the tile details for the new nametable JMP loin10 ; entry .loin18 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 .loin19 LDY YSAV ; Restore Y from YSAV, so that it's preserved CLC ; Clear the C flag for the routine to return RTS ; Return from the subroutine
Name: LOIN (Part 5 of 7) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a line: Line has a steep gradient, step up along y-axis Deep dive: Bresenham's line algorithm Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. If we get here, then: * |delta_y| >= |delta_x| * The line is closer to being vertical than horizontal * We are going to step up along the y-axis * We potentially swap coordinates to make sure Y1 >= Y2
.STPY LDY Y1 ; Set A = Y = Y1 TYA LDX X1 ; Set X = X1 CPY Y2 ; If Y1 = Y2, jump up to loin18 to return from the BEQ loin18 ; subroutine as there is no line to draw BCS LI15 ; If Y1 > Y2, jump down to LI15, as the coordinates are ; already in the order that we want DEC SWAP ; Otherwise decrement SWAP from 0 to $FF, to denote that ; we are swapping the coordinates around (though note ; that we don't use this value anywhere, as in the ; original versions of Elite it is used to omit the ; first pixel of each line, which we don't have to do ; in the NES version as it doesn't use EOR plotting) LDA X2 ; Swap the values of X1 and X2 STA X1 STX X2 TAX ; Set X = X1 LDA Y2 ; Swap the values of Y1 and Y2 STA Y1 STY Y2 TAY ; Set Y = A = Y1 .LI15 ; By this point we know the line is vertical-ish and ; Y1 >= Y2, so we're going from top to bottom as we go ; from Y1 to Y2 ; The following section calculates: ; ; P = P / Q ; = |delta_x| / |delta_y| ; ; using the log tables at logL and log to calculate: ; ; A = log(P) - log(Q) ; = log(|delta_x|) - log(|delta_y|) ; ; by first subtracting the low bytes of the logarithms ; from the table at LogL, and then subtracting the high ; bytes from the table at log, before applying the ; antilog to get the result of the division and putting ; it in P LDX P ; Set X = |delta_x| BEQ LIfudge ; If |delta_x| = 0, jump to LIfudge to return 0 as the ; result of the division LDA logL,X ; Set A = log(P) - log(Q) LDX Q ; = log(|delta_x|) - log(|delta_y|) SEC ; SBC logL,X ; by first subtracting the low bytes of log(P) - log(Q) BMI LIloG ; If A > 127, jump to LIloG LDX P ; And then subtracting the high bytes of log(P) - log(Q) LDA log,X ; so now A contains the high byte of log(P) - log(Q) LDX Q SBC log,X BCS LIlog3 ; If the subtraction fitted into one byte and didn't ; underflow, then log(P) - log(Q) < 256, so we jump to ; LIlog3 to return a result of 255 TAX ; Otherwise we set A to the A-th entry from the antilog LDA antilog,X ; table so the result of the division is now in A JMP LIlog2 ; Jump to LIlog2 to return the result .LIlog3 LDA #255 ; The division is very close to 1, so set A to the BNE LIlog2 ; closest possible answer to 256, i.e. 255, and jump to ; LIlog2 to return the result (this BNE is effectively a ; JMP as A is never zero) .LIfudge LDA #0 ; Set A = 0 and jump to LIlog2 to return 0 as the result BEQ LIlog2 ; (this BNE is effectively a JMP as A is always zero) .LIloG LDX P ; Subtract the high bytes of log(P) - log(Q) so now A LDA log,X ; contains the high byte of log(P) - log(Q) LDX Q SBC log,X BCS LIlog3 ; If the subtraction fitted into one byte and didn't ; underflow, then log(P) - log(Q) < 256, so we jump to ; LIlog3 to return a result of 255 TAX ; Otherwise we set A to the A-th entry from the LDA antilogODD,X ; antilogODD so the result of the division is now in A .LIlog2 STA P ; Store the result of the division in P, so we have: ; ; P = |delta_x| / |delta_y| LDA X1 ; Set SC2(1 0) = (nameBufferHi 0) + yLookup(Y) + X1 / 8 LSR A ; LSR A ; where yLookup(Y) uses the (yLookupHi yLookupLo) table LSR A ; to convert the pixel y-coordinate in Y into the number CLC ; of the first tile on the row containing the pixel ADC yLookupLo,Y ; STA SC2 ; Adding nameBufferHi and X1 / 8 therefore sets SC2(1 0) LDA nameBufferHi ; to the address of the entry in the nametable buffer ADC yLookupHi,Y ; that contains the tile number for the tile containing STA SC2+1 ; the pixel at (X1, Y), i.e. the line we are drawing TYA ; Set Y = Y mod 8, which is the pixel row within the AND #7 ; character block at which we want to draw the start of TAY ; our line (as each character block has 8 rows) SEC ; Set A = X2 - X1 LDA X2 ; SBC X1 ; This sets the C flag when X1 <= X2 LDA X1 ; Set X = X1 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA TWOS,X ; Fetch a 1-pixel byte from TWOS where pixel X is set STA R ; Store the pixel byte in R LDX Q ; Set X = Q + 1 INX ; = |delta_y| + 1 ; ; We will use Q as the y-axis counter, and we add 1 to ; ensure we include the pixel at each end BCS loin24 ; If X1 <= X2 (which we calculated above) then jump to ; loin24 to draw the line to the left and up JMP loin36 ; If we get here then X1 > X2, so jump to loin36, as we ; need to draw the line to the left and down
Name: LOIN (Part 6 of 7) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a steep line going up and left or down and right Deep dive: Bresenham's line algorithm Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. If we get here, then: * The line is going up and left (no swap) or down and right (swap) * X1 < X2 and Y1 >= Y2 * Draw from (X1, Y1) at top left to (X2, Y2) at bottom right
.loin20 LDY YSAV ; Restore Y from YSAV, so that it's preserved CLC ; Clear the C flag for the routine to return RTS ; Return from the subroutine .loin21 ; If we get here then we are drawing our line in a new ; pattern, so it won't contain any pre-existing content LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC CLC ; Clear the C flag for the additions below LDX Q ; Set X to the value of the x-axis counter .loin22 LDA R ; Fetch the pixel byte from R STA (SC),Y ; Store R into screen memory at SC(1 0) - we don't need ; to merge it with whatever is there, as we just started ; drawing in a new tile DEX ; Decrement the y-coordinate counter in X BEQ loin20 ; If we have just reached the end of the line along the ; y-axis, jump to loin20 to return from the subroutine LDA S ; Set S = S + P to update the slope error ADC P STA S BCC loin23 ; If the addition didn't overflow, jump to loin23 to ; skip the following LSR R ; Shift the single pixel in R to the right to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BCS loin28 ; If the pixel fell out of the right end of R into the ; C flag, then jump to loin28 to rotate it into the left ; end and move right by a character block .loin23 DEY ; Decrement Y to point to move to the pixel line above BPL loin22 ; If Y is still positive then we have not yet gone past ; the top of the character block, so jump to loin22 to ; draw the next pixel ; Otherwise we just gone past the top of the current ; character block, so we need to move up into the ; character block above by setting Y and SC2(1 0) LDY #7 ; Set Y to point to the bottom pixel row of the block ; above ; If we get here then the C flag is clear, as we either ; jumped to loin23 using a BCC, or we passed through a ; BCS to get to loin23, so the SBC #31 below actually ; subtracts 32 LDA SC2 ; Subtract 32 from SC2(1 0) to get the tile number on SBC #31 ; the row above (as there are 32 tiles on each row) STA SC2 BCS loin24 DEC SC2+1 ; Fall through into loin24 to fetch the correct tile ; number for the new character block and continue ; drawing .loin24 ; This is the entry point for this part (we jump here ; from part 5 when the line is steep and X1 <= X2) ; ; We jump here with X containing the y-axis counter, ; i.e. the number of steps we need to take along the ; y-axis when drawing the line STX Q ; Store the updated y-axis counter in Q SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; If the nametable buffer entry is non-zero for the tile LDA (SC2,X) ; containing the pixel that we want to draw, then a BNE loin25 ; pattern has already been allocated to this entry, so ; skip the following LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ loin29 ; patterns to use for drawing lines and pixels, so jump ; to loin29 to move on to the next pixel in the line STA (SC2,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; pattern to cover the pixel that we want to draw by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be added to ; the nametable the next time we need to draw lines or ; pixels into a pattern JMP loin21 ; Jump to loin21 to calculate the pattern buffer address ; for the new tile and continue drawing .loin25 ; If we get here then we are drawing our line in a ; pattern that was already in the nametable, so it might ; contain pre-existing content LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC CLC ; Clear the C flag for the additions below LDX Q ; Set X to the value of the x-axis counter .loin26 ; We now loop along the line from left to right, using P ; as a decreasing counter, and at each count we plot a ; single pixel using the pixel mask in R LDA R ; Fetch the pixel byte from R ORA (SC),Y ; Store R into screen memory at SC(1 0), using OR logic STA (SC),Y ; so it merges with whatever is already on-screen DEX ; Decrement the y-coordinate counter in X BEQ loin20 ; If we have just reached the end of the line along the ; y-axis, jump to loin20 to return from the subroutine LDA S ; Set S = S + P to update the slope error ADC P STA S BCC loin27 ; If the addition didn't overflow, jump to loin27 to ; skip the following LSR R ; Shift the single pixel in R to the right to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BCS loin28 ; If the pixel fell out of the right end of R into the ; C flag, then jump to loin28 to rotate it into the left ; end and move right by a character block .loin27 DEY ; Decrement Y to point to move to the pixel line above BPL loin26 ; If Y is still positive then we have not yet gone past ; the top of the character block, so jump to loin26 to ; draw the next pixel ; Otherwise we just gone past the top of the current ; character block, so we need to move up into the ; character block above by setting Y and SC2(1 0) LDY #7 ; Set Y to point to the bottom pixel row of the block ; above ; If we get here then the C flag is clear, as we either ; jumped to loin27 using a BCC, or we passed through a ; BCS to get to loin27, so the SBC #31 below actually ; subtracts 32 LDA SC2 ; Subtract 32 from SC2(1 0) to get the tile number on SBC #31 ; the row above (as there are 32 tiles on each row) and STA SC2 ; jump to loin24 to fetch the correct tile number for BCS loin24 ; the new character block and continue drawing (this DEC SC2+1 ; BNE is effectively a JMP as the high byte of SC2(1 0) BNE loin24 ; will never be zero (the nametable buffers start at ; address $7000, so the high byte is at least $70) .loin28 ; If we get here, then we just shifted the pixel out of ; the right end of R, so we now need to put it back into ; the left end of R and move to the right by one ; character block ROR R ; We only reach here via a BCS, so this rotates a 1 into ; the left end of R and clears the C flag INC SC2 ; Increment SC2(1 0) to point to the next tile number to BNE P%+4 ; the right in the nametable buffer INC SC2+1 DEY ; Decrement Y to point to move to the pixel line above BPL loin24 ; If Y is still positive then we have not yet gone past ; the top of the character block, so jump to loin24 to ; draw the next pixel LDY #7 ; Set Y to point to the bottom pixel row of the block ; above LDA SC2 ; Subtract 32 from SC2(1 0) to get the tile number on SBC #31 ; the row above (as there are 32 tiles on each row) and STA SC2 ; jump to loin24 to fetch the correct tile number for BCS loin24 ; the new character block and continue drawing DEC SC2+1 JMP loin24 .loin29 ; If we get here then we have run out of tiles to ; allocate to the line drawing, so we continue with the ; same calculations, but don't actually draw anything in ; this character block LDX Q ; Set X to the value of the x-axis counter .loin30 DEX ; Decrement the x-axis counter in X BEQ loin32 ; If we have just reached the end of the line along the ; x-axis, jump to loin32 to return from the subroutine LDA S ; Set S = S + P to update the slope error ADC P STA S BCC loin31 ; If the addition didn't overflow, jump to loin31 to ; skip the following LSR R ; Shift the single pixel in R to the right to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BCS loin28 ; If the pixel fell out of the right end of R into the ; C flag, then jump to loin28 to rotate it into the left ; end and move right by a character block .loin31 DEY ; Decrement Y to point to move to the pixel line above BPL loin30 ; If Y is still positive then we have not yet gone past ; the top of the character block, so jump to loin30 to ; draw the next pixel ; Otherwise we just gone past the top of the current ; character block, so we need to move up into the ; character block above by setting Y and SC2(1 0) LDY #7 ; Set Y to point to the bottom pixel row of the block ; above ; If we get here then the C flag is clear, as we either ; jumped to loin31 using a BCC, or we passed through a ; BCS to get to loin31, so the SBC #31 below actually ; subtracts 32 LDA SC2 ; Subtract 32 from SC2(1 0) to get the tile number on SBC #31 ; the row above (as there are 32 tiles on each row) STA SC2 BCS P%+4 DEC SC2+1 JMP loin24 ; Jump to loin24 to fetch the correct tile number for ; the new character block and continue drawing .loin32 LDY YSAV ; Restore Y from YSAV, so that it's preserved CLC ; Clear the C flag for the routine to return RTS ; Return from the subroutine
Name: LOIN (Part 7 of 7) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a steep line going up and right or down and left Deep dive: Bresenham's line algorithm Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

This routine draws a line from (X1, Y1) to (X2, Y2). It has multiple stages. If we get here, then: * The line is going up and right (no swap) or down and left (swap) * X1 >= X2 and Y1 >= Y2 * Draw from (X1, Y1) at bottom left to (X2, Y2) at top right
.loin33 LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC CLC ; Clear the C flag for the additions below LDX Q ; Set X to the value of the x-axis counter .loin34 LDA R ; Fetch the pixel byte from R STA (SC),Y ; Store R into screen memory at SC(1 0) - we don't need ; to merge it with whatever is there, as we just started ; drawing in a new tile DEX ; Decrement the y-coordinate counter in X BEQ loin32 ; If we have just reached the end of the line along the ; y-axis, jump to loin32 to return from the subroutine LDA S ; Set S = S + P to update the slope error ADC P STA S BCC loin35 ; If the addition didn't overflow, jump to loin35 to ; skip the following ASL R ; Shift the single pixel in R to the left to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BCS loin40 ; If the pixel fell out of the left end of R into the ; C flag, then jump to loin40 to rotate it into the ; left end and move left by a character block .loin35 DEY ; Decrement Y to point to move to the pixel line above BPL loin34 ; If Y is still positive then we have not yet gone past ; the top of the character block, so jump to loin34 to ; draw the next pixel ; Otherwise we just gone past the top of the current ; character block, so we need to move up into the ; character block above by setting Y and SC2(1 0) LDY #7 ; Set Y to point to the bottom pixel row of the block ; above ; If we get here then the C flag is clear, as we either ; jumped to loin35 using a BCC, or we passed through a ; BCS to get to loin35, so the SBC #31 below actually ; subtracts 32 LDA SC2 ; Subtract 32 from SC2(1 0) to get the tile number on SBC #31 ; the row above (as there are 32 tiles on each row) STA SC2 BCS loin36 DEC SC2+1 ; Fall through into loin36 to fetch the correct tile ; number for the new character block and continue ; drawing .loin36 ; This is the entry point for this part (we jump here ; from part 5 when the line is steep and X1 > X2) ; ; We jump here with X containing the y-axis counter, ; i.e. the number of steps we need to take along the ; y-axis when drawing the line STX Q ; Store the updated y-axis counter in Q SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; If the nametable buffer entry is non-zero for the tile LDA (SC2,X) ; containing the pixel that we want to draw, then a BNE loin37 ; pattern has already been allocated to this entry, so ; skip the following LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ loin41 ; patterns to use for drawing lines and pixels, so jump ; to loin41 to keep going with the line-drawing ; calculations, but without drawing anything in this ; pattern STA (SC2,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; pattern to cover the pixel that we want to draw by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be added to ; the nametable the next time we need to draw lines or ; pixels into a pattern JMP loin33 ; Jump to loin33 to calculate the pattern buffer address ; for the new tile and continue drawing .loin37 ; If we get here then we are drawing our line in a ; pattern that was already in the nametable, so it might ; contain pre-existing content LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC CLC ; Clear the C flag for the additions below LDX Q ; Set X to the value of the x-axis counter .loin38 ; We now loop along the line from right to left, using P ; as a decreasing counter, and at each count we plot a ; single pixel using the pixel mask in R LDA R ; Fetch the pixel byte from R ORA (SC),Y ; Store R into screen memory at SC(1 0), using OR logic STA (SC),Y ; so it merges with whatever is already on-screen DEX ; Decrement the y-coordinate counter in X BEQ loin45 ; If we have just reached the end of the line along the ; y-axis, jump to loin45 to return from the subroutine LDA S ; Set S = S + P to update the slope error ADC P STA S BCC loin39 ; If the addition didn't overflow, jump to loin39 to ; skip the following ASL R ; Shift the single pixel in R to the left to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BCS loin40 ; If the pixel fell out of the left end of R into the ; C flag, then jump to loin40 to rotate it into the ; left end and move left by a character block .loin39 DEY ; Decrement Y to point to move to the pixel line above BPL loin38 ; If Y is still positive then we have not yet gone past ; the top of the character block, so jump to loin38 to ; draw the next pixel ; Otherwise we just gone past the top of the current ; character block, so we need to move up into the ; character block above by setting Y and SC2(1 0) LDY #7 ; Set Y to point to the bottom pixel row of the block ; above ; If we get here then the C flag is clear, as we either ; jumped to loin39 using a BCC, or we passed through a ; BCS to get to loin39, so the SBC #31 below actually ; subtracts 32 LDA SC2 ; Subtract 32 from SC2(1 0) to get the tile number on SBC #31 ; the row above (as there are 32 tiles on each row) and STA SC2 ; jump to loin36 to fetch the correct tile number for BCS loin36 ; the new character block and continue drawing DEC SC2+1 JMP loin36 .loin40 ; If we get here, then we just shifted the pixel out of ; the left end of R, so we now need to put it back into ; the right end of R and move to the left by one ; character block ROL R ; We only reach here via a BCS, so this rotates a 1 into ; the right end of R and clears the C flag LDA SC2 ; Decrement SC2(1 0) to point to the next tile number to BNE P%+4 ; the left in the nametable buffer DEC SC2+1 DEC SC2 DEY ; Decrement Y to point to move to the pixel line above BPL loin36 ; If Y is still positive then we have not yet gone past ; the top of the character block, so jump to loin36 to ; draw the next pixel LDY #7 ; Set Y to point to the bottom pixel row of the block ; above LDA SC2 ; Subtract 32 from SC2(1 0) to get the tile number on SBC #31 ; the row above (as there are 32 tiles on each row) and STA SC2 ; jump to loin36 to fetch the correct tile number for BCS loin36 ; the new character block and continue drawing DEC SC2+1 JMP loin36 .loin41 ; If we get here then we have run out of tiles to ; allocate to the line drawing, so we continue with the ; same calculations, but don't actually draw anything in ; this character block LDX Q ; Set X to the value of the x-axis counter .loin42 DEX ; Decrement the x-axis counter in X BEQ loin44 ; If we have just reached the end of the line along the ; x-axis, jump to loin44 to return from the subroutine LDA S ; Set S = S + P to update the slope error ADC P STA S BCC loin43 ; If the addition didn't overflow, jump to loin43 to ; skip the following ASL R ; Shift the single pixel in R to the left to step along ; the x-axis, so the next pixel we plot will be at the ; next x-coordinate along BCS loin40 ; If the pixel fell out of the left end of R into the ; C flag, then jump to loin40 to rotate it into the ; left end and move left by a character block .loin43 DEY ; Decrement Y to point to move to the pixel line above BPL loin42 ; If Y is still positive then we have not yet gone past ; the top of the character block, so jump to loin42 to ; draw the next pixel ; Otherwise we just gone past the top of the current ; character block, so we need to move up into the ; character block above by setting Y and SC2(1 0) LDY #7 ; Set Y to point to the bottom pixel row of the block ; above ; If we get here then the C flag is clear, as we either ; jumped to loin43 using a BCC, or we passed through a ; BCS to get to loin43, so the SBC #31 below actually ; subtracts 32 LDA SC2 ; Subtract 32 from SC2(1 0) to get the tile number on SBC #31 ; the row above (as there are 32 tiles on each row) STA SC2 BCS P%+4 DEC SC2+1 JMP loin36 ; Jump to loin36 to fetch the correct tile number for ; the new character block and continue drawing .loin44 LDY YSAV ; Restore Y from YSAV, so that it's preserved CLC ; Clear the C flag for the routine to return RTS ; Return from the subroutine .loin45 LDY YSAV ; Restore Y from YSAV, so that it's preserved CLC ; Clear the C flag for the routine to return RTS ; Return from the subroutine
Name: DrawSunRowOfBlocks [Show more] Type: Subroutine Category: Drawing suns Summary: Draw a row of character blocks that contain sunlight, silhouetting any existing content against the sun
Context: See this subroutine on its own page References: This subroutine is called as follows: * SUN (Part 2 of 2) calls DrawSunRowOfBlocks

This routine fills a row of whole character blocks with sunlight, turning any existing content into a black silhouette on the cyan sun. It effectively fills the character blocks containing the horizontal pixel line (P, Y) to (P+1, Y).
Arguments: P A pixel x-coordinate in the character block from which we start the fill P+1 A pixel x-coordinate in the character block where we finish the fill Y A pixel y-coordinate on the character row to fill
Returns: Y Y is preserved
.DrawSunRowOfBlocks 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 STY YSAV ; Store Y in YSAV so we can retrieve it below LDA P ; Set SC2(1 0) = (nameBufferHi 0) + yLookup(Y) + P * 8 LSR A ; LSR A ; where yLookup(Y) uses the (yLookupHi yLookupLo) table LSR A ; to convert the pixel y-coordinate in Y into the number CLC ; of the first tile on the row containing the pixel ADC yLookupLo,Y ; STA SC2 ; Adding nameBufferHi and P * 8 therefore sets SC2(1 0) LDA nameBufferHi ; to the address of the entry in the nametable buffer ADC yLookupHi,Y ; that contains the tile number for the tile containing STA SC2+1 ; the pixel at (P, Y) LDA P+1 ; Set Y = (P+1 - P) * 8 - 1 SEC ; SBC P ; So Y is the number of tiles we need to fill in the row LSR A LSR A LSR A TAY DEY .fill1 LDA (SC2),Y ; If the nametable entry for the Y-th tile is non-zero, BNE fill2 ; then there is already something there, so jump to ; fill2 to fill this tile using EOR logic (so the pixels ; that are already there are still visible against the ; sun, as black pixels on the sun's cyan background) LDA #51 ; Otherwise the nametable entry is zero, which is just STA (SC2),Y ; the background, so set this tile to pattern 51 DEY ; Decrement the tile counter in Y BPL fill1 ; Loop back until we have filled the entire row of tiles LDY YSAV ; Retrieve the value of Y we stored above RTS ; Return from the subroutine .fill2 ; If we get here then A contains the pattern number of ; the non-empty tile that we want to fill, so we now ; need to fill that pattern in the pattern buffer while ; keeping the existing content STY T ; Store Y in T so we can retrieve it below LDY pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STY SC+1 ; = (pattBufferHiAddr A*8) ASL A ; ROL SC+1 ; This is the address of pattern number A in the current ASL A ; pattern buffer, as each pattern in the buffer consists ROL SC+1 ; of eight bytes ASL A ; ROL SC+1 ; So this is the address of the pattern for the tile STA SC ; that we want to fill, so now to fill it SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDY #7 ; We now loop through each pixel row within this tile's ; pattern, filling the whole pattern with cyan, but ; EOR'ing with the pattern that is already there so it ; is still visible against the sun, as black pixels on ; the sun's cyan background .fill3 LDA #%11111111 ; Invert the Y-th pixel row by EOR'ing with %11111111 EOR (SC),Y STA (SC),Y DEY ; Decrement Y to point to the pixel line above BPL fill3 ; Loop back until we have filled all 8 pixel lines in ; the pattern LDY T ; Retrieve the value of Y we stored above, so it now ; contains the tile counter from the loop at fill1 DEY ; Decrement the tile counter in Y, as we just filled a ; tile BPL fill1 ; If there are still more tiles to fill on this row, ; loop back to fill1 to continue filling them LDY YSAV ; Retrieve the value of Y we stored above RTS ; Return from the subroutine
Name: HLOIN (Part 1 of 5) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a horizontal line from (X1, Y) to (X2, Y) using EOR logic Deep dive: Drawing lines in the NES version
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawLaunchBox calls HLOIN * DrawSunEdgeLeft calls HLOIN * DrawSunEdgeRight calls HLOIN * LL164 calls HLOIN * SUN (Part 2 of 2) calls HLOIN

Arguments: X1 The screen x-coordinate of the start of the line X2 The screen x-coordinate of the end of the line Y The screen y-coordinate of the line
Returns: Y Y is preserved X2 X2 is decremented
.hlin1 JMP hlin23 ; Jump to hlin23 to draw the line when it's all within ; one character block LDY YSAV ; Restore Y from YSAV, so that it's preserved .hlin2 RTS ; Return from the subroutine .HLOIN 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 STY YSAV ; Store Y into YSAV, so we can preserve it across the ; call to this subroutine LDX X1 ; Set X = X1 CPX X2 ; If X1 = X2 then the start and end points are the same, BEQ hlin2 ; so return from the subroutine (as hlin2 contains ; an RTS) BCC hlin3 ; If X1 < X2, jump to hlin3 to skip the following code, ; as (X1, Y) is already the left point LDA X2 ; Swap the values of X1 and X2, so we know that (X1, Y) STA X1 ; is on the left and (X2, Y) is on the right STX X2 TAX ; Set X = X1 once again .hlin3 DEC X2 ; Decrement X2 so we do not draw a pixel at the end ; point TXA ; Set SC2(1 0) = (nameBufferHi 0) + yLookup(Y) + X1 / 8 LSR A ; LSR A ; where yLookup(Y) uses the (yLookupHi yLookupLo) table LSR A ; to convert the pixel y-coordinate in Y into the number CLC ; of the first tile on the row containing the pixel ADC yLookupLo,Y ; STA SC2 ; Adding nameBufferHi and X1 / 8 therefore sets SC2(1 0) LDA nameBufferHi ; to the address of the entry in the nametable buffer ADC yLookupHi,Y ; that contains the tile number for the tile containing STA SC2+1 ; the pixel at (X1, Y), i.e. the line we are drawing TYA ; Set Y = Y mod 8, which is the pixel row within the AND #7 ; character block at which we want to draw the start of TAY ; our line (as each character block has 8 rows) ; ; As we are drawing a horizontal line, we do not need to ; vary the value of Y, as we will always want to draw on ; the same pixel row within each character block TXA ; Set T = X1 with bits 0-2 cleared AND #%11111000 ; STA T ; Each character block contains 8 pixel rows, so to get ; the address of the first byte in the character block ; that we need to draw into, as an offset from the start ; of the row, we clear bits 0-2 ; ; T is therefore the offset within the row of the start ; of the line at x-coordinate X1 LDA X2 ; Set A = X2 with bits 0-2 cleared AND #%11111000 ; SEC ; A is therefore the offset within the row of the end ; of the line at x-coordinate X2 SBC T ; Set A = A - T ; ; So A contains the width of the line in terms of pixel ; bytes (which is the same as the number of character ; blocks that the line spans, less 1 and multiplied by ; 8) BEQ hlin1 ; If the line starts and ends in the same character ; block then A will be zero, so jump to hlin23 via hlin1 ; to draw the line when it's all within one character ; block LSR A ; Otherwise set R = A / 8 LSR A ; LSR A ; So R contains the number of character blocks that the STA R ; line spans, less 1 (so R = 0 means it spans one block, ; R = 1 means it spans two blocks, and so on)
Name: HLOIN (Part 2 of 5) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw the left end of the line Deep dive: Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
; We now start the drawing process, beginning with the ; left end of the line, whose nametable entry is in ; SC2(1 0) SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; If the nametable buffer entry is non-zero for the tile LDA (SC2,X) ; containing the pixels that we want to draw, then a BNE hlin5 ; pattern has already been allocated to this entry, so ; skip the following LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ hlin4 ; patterns to use for drawing lines and pixels, so jump ; to hlin9 via hlin4 to move on to the next character ; block to the right, as we don't have enough patterns ; to draw the left end of the line STA (SC2,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; pattern to cover the pixels that we want to draw by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be used the ; next time we need to draw lines or pixels into a ; pattern JMP hlin7 ; Jump to hlin7 to draw the line, starting by drawing ; the left end into the newly allocated pattern number ; in A .hlin4 JMP hlin9 ; Jump to hlin9 to move right by one character block ; without drawing anything .hlin5 ; If we get here then A contains the tile number that's ; already allocated to this part of the line in the ; nametable buffer CMP #60 ; If A >= 60, then the pattern that's already allocated BCS hlin7 ; is one of the patterns we have reserved for drawing, ; so jump to hlin7 to draw the line, starting by drawing ; the left end into the pattern number in A CMP #37 ; If A < 37, then the pattern that's already allocated BCC hlin4 ; is one of the icon bar tiles, so jump to hlin9 via ; hlin4 to move right by one character block without ; drawing anything, as we can't draw on the icon bar ; If we get here then 37 <= A <= 59, so the pattern ; that's already allocated is one of the pre-rendered ; patterns containing horizontal and vertical lines ; ; We don't want to draw over the top of the pre-rendered ; patterns as that will break them, so instead we make a ; copy of the pre-rendered pattern into a newly ; allocated pattern, and then draw our line into the ; this new pattern, thus preserving what's already shown ; on-screen while still drawing our new line LDX pattBufferHiDiv8 ; Set SC3(1 0) = (pattBufferHiDiv8 A) * 8 STX SC3+1 ; = (pattBufferHi A) + A * 8 ASL A ; ROL SC3+1 ; So SC3(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC3+1 ; pattern data), which means SC3(1 0) points to the ASL A ; pattern data for the tile containing the pre-rendered ROL SC3+1 ; pattern that we want to copy STA SC3 LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ hlin4 ; patterns for drawing lines and pixels, so jump to ; hlin9 via hlin4 to move right by one character block ; without drawing anything, as we don't have enough ; patterns to draw the left end of the line LDX #0 ; Otherwise firstFreePattern contains the number of the STA (SC2,X) ; next available pattern for drawing, so allocate this ; pattern to cover the pixels that we want to copy by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be used the ; next time we need to draw lines or pixels into a ; pattern LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the pattern we just fetched ROL SC+1 STA SC ; We now have a new pattern in SC(1 0) into which we can ; draw the left end of our line, so we now need to copy ; the pre-rendered pattern that we want to draw on top ; of ; ; Each pattern is made up of eight bytes, so we simply ; need to copy eight bytes from SC3(1 0) to SC(1 0) STY T ; Store Y in T so we can retrieve it after the following ; loop LDY #7 ; We now copy eight bytes from SC3(1 0) to SC(1 0), so ; set a counter in Y .hlin6 LDA (SC3),Y ; Copy the Y-th byte of SC3(1 0) to the Y-th byte of STA (SC),Y ; SC(1 0) DEY ; Decrement the counter BPL hlin6 ; Loop back until we have copied all eight bytes LDY T ; Restore the value of Y from before the loop, so it ; once again contains the pixel row offset within the ; each character block for the line we are drawing JMP hlin8 ; Jump to hlin8 to draw the left end of the line into ; the tile that we just copied .hlin7 ; If we get here then we have either allocated a new ; pattern number for the line, or the pattern number ; already allocated to this part of the line is >= 60, ; which is a pattern into which we can draw ; ; In either case the pattern number is in A LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC .hlin8 ; We now draw the left end of our horizontal line LDA X1 ; Set X = X1 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA TWFR,X ; Fetch a ready-made byte with X pixels filled in at the ; right end of the byte (so the filled pixels start at ; point X and go all the way to the end of the byte), ; which is the shape we want for the left end of the ; line EOR (SC),Y ; Store this into the pattern buffer at SC(1 0), using STA (SC),Y ; EOR logic so it merges with whatever is already ; on-screen, so we have now drawn the line's left cap .hlin9 INC SC2 ; Increment SC2(1 0) to point to the next tile number to BNE P%+4 ; the right in the nametable buffer INC SC2+1 LDX R ; Fetch the number of character blocks in which we need ; to draw, which we stored in R above DEX ; If R = 1, then we only have the right cap to draw, so BNE hlin10 ; jump to hlin17 to draw the right end of the line JMP hlin17 .hlin10 STX R ; Otherwise we haven't reached the right end of the line ; yet, so decrement R as we have just drawn one block
Name: HLOIN (Part 3 of 5) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw the middle part of the line Deep dive: Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
; We now draw the middle part of the line (i.e. the part ; between the left and right caps) .hlin11 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; If the nametable buffer entry is zero for the tile LDA (SC2,X) ; containing the pixels that we want to draw, then a BEQ hlin13 ; pattern has not yet been allocated to this entry, so ; jump to hlin13 to allocate a new pattern ; If we get here then A contains the pattern number ; that's already allocated to this part of the line in ; the nametable buffer CMP #60 ; If A < 60, then the pattern that's already allocated BCC hlin15 ; is either an icon bar pattern, or one of the ; pre-rendered patterns containing horizontal and ; vertical lines, so jump to hlin15 to process drawing ; on top of the pre-rendered pattern ; If we get here then the pattern number already ; allocated to this part of the line is >= 60, which is ; a pattern into which we can draw ; ; The pattern number is in A LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC LDA #%11111111 ; Set A to a pixel byte containing eight pixels in a row EOR (SC),Y ; Store this into the pattern buffer at SC(1 0), using STA (SC),Y ; EOR logic so it merges with whatever is already ; on-screen, so we have now drawn one character block ; of the middle portion of the line .hlin12 INC SC2 ; Increment SC2(1 0) to point to the next tile number to BNE P%+4 ; the right in the nametable buffer INC SC2+1 DEC R ; Decrement the number of character blocks in which we ; need to draw, as we have just drawn one block BNE hlin11 ; If there are still more character blocks to draw, loop ; back to hlin11 to draw the next one JMP hlin17 ; Otherwise we have finished drawing the middle portion ; of the line, so jump to hlin17 to draw the right end ; of the line .hlin13 ; If we get here then there is no pattern allocated to ; the part of the line we want to draw, so we can use ; one of the pre-rendered patterns that contains an ; 8-pixel horizontal line on the correct pixel row ; ; We jump here with X = 0 TYA ; Set A = Y + 37 CLC ; ADC #37 ; Patterns 37 to 44 contain pre-rendered patterns as ; follows: ; ; * Pattern 37 has a horizontal line on pixel row 0 ; * Pattern 38 has a horizontal line on pixel row 1 ; ... ; * Pattern 43 has a horizontal line on pixel row 6 ; * Pattern 44 has a horizontal line on pixel row 7 ; ; So A contains the pre-rendered pattern number that ; contains an 8-pixel line on pixel row Y, and as Y ; contains the offset of the pixel row for the line we ; are drawing, this means A contains the correct pattern ; number for this part of the line STA (SC2,X) ; Display the pre-rendered pattern on-screen by setting ; the nametable entry to A JMP hlin12 ; Jump up to hlin12 to move on to the next character ; block to the right .hlin14 ; If we get here then A + Y = 50, which means we can ; alter the current pre-rendered pattern to draw our ; line ; ; This is how it works. Patterns 44 to 51 contain ; pre-rendered patterns as follows: ; ; * Pattern 44 has a horizontal line on pixel row 7 ; * Pattern 45 is filled from pixel row 7 to row 6 ; * Pattern 46 is filled from pixel row 7 to row 5 ; ... ; * Pattern 50 is filled from pixel row 7 to row 1 ; * Pattern 51 is filled from pixel row 7 to row 0 ; ; Y contains the number of the pixel row for the line we ; are drawing, so if A + Y = 50, this means: ; ; * We want to draw pixel row 0 on top of pattern 50 ; * We want to draw pixel row 1 on top of pattern 49 ; ... ; * We want to draw pixel row 5 on top of pattern 45 ; * We want to draw pixel row 6 on top of pattern 44 ; ; In other words, if A + Y = 50, then we want to draw ; the pixel row just above the rows that are already ; filled in the pre-rendered pattern, which means we ; can simply swap the pre-rendered pattern to the next ; one in the list (e.g. going from four filled lines to ; five filled lines, for example) ; ; We jump here with a BEQ, so the C flag is set for the ; following addition, so the C flag can be used as the ; plus 1 in the two's complement calculation TYA ; Set A = 51 + C + ~Y EOR #$FF ; = 51 + (1 + ~Y) ADC #51 ; = 51 - Y ; ; So A contains the number of the pre-rendered pattern ; that has our horizontal line drawn on pixel row Y, and ; all the lines below that filled, which is what we want STA (SC2,X) ; Display the pre-rendered pattern on-screen by setting ; the nametable entry to A INC SC2 ; Increment SC2(1 0) to point to the next tile number to BNE P%+4 ; the right in the nametable buffer INC SC2+1 DEC R ; Decrement the number of character blocks in which we ; need to draw, as we have just drawn one block BNE hlin11 ; If there are still more character blocks to draw, loop ; back to hlin11 to draw the next one JMP hlin17 ; Otherwise we have finished drawing the middle portion ; of the line, so jump to hlin17 to draw the right end ; of the line .hlin15 ; If we get here then A <= 59, so the pattern that's ; already allocated is either an icon bar pattern, or ; one of the pre-rendered patterns containing horizontal ; and vertical lines ; ; We jump here with the C flag clear, so the addition ; below will work correctly, and with X = 0, so the ; write to (SC2,X) will also work properly STA SC ; Set SC to the number of the tile that is already ; allocated to this part of the screen, so we can ; retrieve it later TYA ; If A + Y = 50, then we are drawing our line just ADC SC ; above the top line of a pre-rendered pattern that is CMP #50 ; filled from the bottom row to the row just below Y, BEQ hlin14 ; so jump to hlin14 to switch this tile to another ; pre-rendered pattern that contains the line we want to ; draw (see hlin14 for a full explanation of this logic) ; If we get here then 37 <= A <= 59, so the pattern ; that's already allocated is one of the pre-rendered ; patterns containing horizontal and vertical lines, but ; isn't one that we can simply replace with another ; pre-rendered pattern ; ; We don't want to draw over the top of the pre-rendered ; patterns as that will break them, so instead we make a ; copy of the pre-rendered pattern into a newly ; allocated pattern, and then draw our line into the ; this new pattern, thus preserving what's already shown ; on-screen while still drawing our new line LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ hlin12 ; patterns to use for drawing lines and pixels, so jump ; to hlin12 to move right by one character block without ; drawing anything, as we don't have enough patterns to ; draw this part of the line INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be used the ; next time we need to draw lines or pixels into a ; pattern STA (SC2,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; tile to contain the pre-rendered pattern that we want ; to copy by setting the nametable entry to the pattern ; number we just fetched LDX pattBufferHiDiv8 ; Set SC3(1 0) = (pattBufferHiDiv8 A) * 8 STX SC3+1 ; = (pattBufferHi A) + A * 8 ASL A ; ROL SC3+1 ; So SC3(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC3+1 ; pattern data), which means SC3(1 0) points to the ASL A ; pattern data for the pattern we just fetched ROL SC3+1 STA SC3 LDA SC ; Set A to the number of the pattern that is already ; allocated to this part of the screen, which we stored ; in SC above LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the pre-rendered ROL SC+1 ; pattern that we want to copy STA SC ; We now have a new pattern in SC3(1 0) into which we ; can draw the left end of our line, so we now need to ; copy the pre-rendered pattern that we want to draw on ; top of ; ; Each pattern is made up of eight bytes, so we simply ; need to copy eight bytes from SC(1 0) to SC3(1 0) STY T ; Store Y in T so we can retrieve it after the following ; loop LDY #7 ; We now copy eight bytes from SC(1 0) to SC3(1 0), so ; set a counter in Y .hlin16 LDA (SC),Y ; Copy the Y-th byte of SC(1 0) to the Y-th byte of STA (SC3),Y ; SC3(1 0) DEY ; Decrement the counter BPL hlin16 ; Loop back until we have copied all eight bytes LDY T ; Restore the value of Y from before the loop, so it ; once again contains the pixel row offset within the ; each character block for the line we are drawing LDA #%11111111 ; Set A to a pixel byte containing eight pixels in a row EOR (SC3),Y ; Store this into the pattern buffer at SC3(1 0), using STA (SC3),Y ; EOR logic so it merges with whatever is already ; on-screen, so we have now drawn one character block ; of the middle portion of the line JMP hlin12 ; Loop back to hlin12 to continue drawing the line in ; the next character block to the right
Name: HLOIN (Part 4 of 5) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw the right end of the line Deep dive: Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.hlin17 ; We now finish off the drawing process with the right ; end of the line SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; If the nametable buffer entry is non-zero for the tile LDA (SC2,X) ; containing the pixels that we want to draw, then a BNE hlin19 ; pattern has already been allocated to this entry, so ; skip the following LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ hlin18 ; patterns to use for drawing lines and pixels, so jump ; to hlin30 via hlin18 to return from the subroutine, as ; we don't have enough patterns to draw the right end ; of the line STA (SC2,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; pattern to cover the pixels that we want to draw by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be used the ; next time we need to draw lines or pixels into a ; drawing JMP hlin21 ; Jump to hlin21 to draw the right end of the line into ; the newly allocated pattern number in A .hlin18 JMP hlin30 ; Jump to hlin30 to return from the subroutine .hlin19 ; If we get here then A contains the tile number that's ; already allocated to this part of the line in the ; nametable buffer CMP #60 ; If A >= 60, then the pattern that's already allocated BCS hlin21 ; is one of the patterns we have reserved for drawing, ; so jump to hlin21 to draw the right end of the line CMP #37 ; If A < 37, then the pattern that's already allocated BCC hlin18 ; is one of the icon bar tiles, so jump to hlin30 via ; hlin18 to return from the subroutine, as we can't draw ; on the icon bar ; If we get here then 37 <= A <= 59, so the pattern ; that's already allocated is one of the pre-rendered ; patterns containing horizontal and vertical lines ; ; We don't want to draw over the top of the pre-rendered ; patterns as that will break them, so instead we make a ; copy of the pre-rendered pattern into a newly ; allocated pattern, and then draw our line into the ; this new pattern, thus preserving what's already shown ; on-screen while still drawing our new line LDX pattBufferHiDiv8 ; Set SC3(1 0) = (pattBufferHiDiv8 A) * 8 STX SC3+1 ; = (pattBufferHi A) + A * 8 ASL A ; ROL SC3+1 ; So SC3(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC3+1 ; pattern data), which means SC3(1 0) points to the ASL A ; pattern data for the tile containing the pre-rendered ROL SC3+1 ; pattern that we want to copy STA SC3 LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ hlin18 ; patterns for drawing lines and pixels, so jump to ; hlin30 via hlin18 to return from the subroutine, as we ; don't have enough patterns to draw the right end ; of the line LDX #0 ; Otherwise firstFreePattern contains the number of the STA (SC2,X) ; next available pattern for drawing, so allocate this ; tile to contain the pre-rendered pattern that we want ; to copy by setting the nametable entry to the pattern ; number we just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be used the ; next time we need to draw lines or pixels into a ; pattern LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the pattern we just fetched ROL SC+1 STA SC ; We now have a new pattern in SC(1 0) into which we can ; draw the right end of our line, so we now need to copy ; the pre-rendered pattern that we want to draw on top ; of ; ; Each pattern is made up of eight bytes, so we simply ; need to copy eight bytes from SC3(1 0) to SC(1 0) STY T ; Store Y in T so we can retrieve it after the following ; loop LDY #7 ; We now copy eight bytes from SC3(1 0) to SC(1 0), so ; set a counter in Y .hlin20 LDA (SC3),Y ; Copy the Y-th byte of SC3(1 0) to the Y-th byte of STA (SC),Y ; SC(1 0) DEY ; Decrement the counter BPL hlin20 ; Loop back until we have copied all eight bytes LDY T ; Restore the value of Y from before the loop, so it ; once again contains the pixel row offset within the ; each character block for the line we are drawing JMP hlin22 ; Jump to hlin22 to draw the right end of the line into ; the tile that we just copied .hlin21 ; If we get here then we have either allocated a new ; pattern number for the line, or the pattern number ; already allocated to this part of the line is >= 60, ; which is a pattern into which we can draw ; ; In either case the pattern number is in A LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC .hlin22 ; We now draw the right end of our horizontal line LDA X2 ; Set X = X2 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line ends (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA TWFL,X ; Fetch a ready-made byte with X pixels filled in at the ; left end of the byte (so the filled pixels start at ; the left edge and go up to point X), which is the ; shape we want for the right end of the line JMP hlin29 ; Jump to hlin29 to poke the pixel byte into the pattern ; buffer
Name: HLOIN (Part 5 of 5) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw the line when it's all within one character block Deep dive: Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.hlin23 ; If we get here then the line starts and ends in the ; same character block SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; If the nametable buffer entry is non-zero for the tile LDA (SC2,X) ; containing the pixels that we want to draw, then a BNE hlin25 ; pattern has already been allocated to this entry, so ; skip the following LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ hlin24 ; patterns to use for drawing lines and pixels, so jump ; to hlin30 via hlin24 to return from the subroutine, as ; we don't have enough patterns to draw the line STA (SC2,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; pattern to cover the pixels that we want to draw by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be used the ; next time we need to draw lines or pixels into a ; pattern JMP hlin27 ; Jump to hlin27 to draw the line into the newly ; allocated tile number in A .hlin24 JMP hlin30 ; Jump to hlin30 to return from the subroutine .hlin25 ; If we get here then A contains the tile number that's ; already allocated to this part of the line in the ; nametable buffer CMP #60 ; If A >= 60, then the pattern that's already allocated BCS hlin27 ; is one of the patterns we have reserved for drawing, ; so jump to hlin27 to draw the line into the pattern ; number in A CMP #37 ; If A < 37, then the pattern that's already allocated BCC hlin24 ; is one of the icon bar patterns, so jump to hlin30 via ; hlin24 to return from the subroutine, as we can't draw ; on the icon bar ; If we get here then 37 <= A <= 59, so the pattern ; that's already allocated is one of the pre-rendered ; patterns containing horizontal and vertical lines ; ; We don't want to draw over the top of the pre-rendered ; patterns as that will break them, so instead we make a ; copy of the pre-rendered pattern into a newly ; allocated pattern, and then draw our line into the ; this new pattern, thus preserving what's already shown ; on-screen while still drawing our new line LDX pattBufferHiDiv8 ; Set SC3(1 0) = (pattBufferHiDiv8 A) * 8 STX SC3+1 ; = (pattBufferHi A) + A * 8 ASL A ; ROL SC3+1 ; So SC3(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC3+1 ; pattern data), which means SC3(1 0) points to the ASL A ; pattern data for the tile containing the pre-rendered ROL SC3+1 ; pattern that we want to copy STA SC3 LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ hlin24 ; patterns for drawing lines and pixels, so jump to ; hlin30 via hlin24 to return from the subroutine, as we ; don't have enough patterns to draw the line LDX #0 ; Otherwise firstFreePattern contains the number of the STA (SC2,X) ; next available pattern for drawing, so allocate this ; tile to contain the pre-rendered pattern that we want ; to copy by setting the nametable entry to the pattern ; number we just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be used the ; next time we need to draw lines or pixels into a ; pattern LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the pattern we just fetched ROL SC+1 STA SC ; We now have a new pattern in SC(1 0) into which we can ; draw our line, so we now need to copy the pattern of ; the pre-rendered pattern that we want to draw on top ; of ; ; Each pattern is made up of eight bytes, so we simply ; need to copy eight bytes from SC3(1 0) to SC(1 0) STY T ; Store Y in T so we can retrieve it after the following ; loop LDY #7 ; We now copy eight bytes from SC3(1 0) to SC(1 0), so ; set a counter in Y .hlin26 LDA (SC3),Y ; Copy the Y-th byte of SC3(1 0) to the Y-th byte of STA (SC),Y ; SC(1 0) DEY ; Decrement the counter BPL hlin26 ; Loop back until we have copied all eight bytes LDY T ; Restore the value of Y from before the loop, so it ; once again contains the pixel row offset within the ; each character block for the line we are drawing JMP hlin28 ; Jump to hlin28 to draw the line into the tile that ; we just copied .hlin27 ; If we get here then we have either allocated a new ; pattern number for the line, or the pattern number ; already allocated to this part of the line is >= 60, ; which is a pattern into which we can draw ; ; In either case the pattern number is in A LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC .hlin28 ; We now draw our horizontal line into the relevant ; character block LDA X1 ; Set X = X1 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA TWFR,X ; Fetch a ready-made byte with X pixels filled in at the ; right end of the byte (so the filled pixels start at ; point X and go all the way to the end of the byte), ; which is the shape we want for the left end of the ; line STA T ; Store the pixel shape for the right end of the line in ; T LDA X2 ; Set X = X2 mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line ends (as TAX ; each pixel line in the character block is 8 pixels ; wide) LDA TWFL,X ; Fetch a ready-made byte with X pixels filled in at the ; left end of the byte (so the filled pixels start at ; the left edge and go up to point X), which is the ; shape we want for the right end of the line AND T ; Set A to the overlap of the pixel byte for the left ; end of the line (in T) and the right end of the line ; (in A) by AND'ing them together, which gives us the ; pixels that are in the horizontal line we want to draw .hlin29 EOR (SC),Y ; Store this into the pattern buffer at SC(1 0), using STA (SC),Y ; EOR logic so it merges with whatever is already ; on-screen, so we have now drawn our entire horizontal ; line within this one character block .hlin30 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDY YSAV ; Restore Y from YSAV, so that it's preserved RTS ; Return from the subroutine
Name: DrawVerticalLine (Part 1 of 3) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a vertical line from (X1, Y1) to (X1, Y2) Deep dive: Drawing lines in the NES version
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawLaunchBox calls DrawVerticalLine

Arguments: X1 The screen x-coordinate of the line Y1 The screen y-coordinate of the start of the line Y2 The screen y-coordinate of the end of the line
Returns: Y Y is preserved
.DrawVerticalLine 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 STY YSAV ; Store Y into YSAV, so we can preserve it across the ; call to this subroutine LDY Y1 ; Set Y = Y1 CPY Y2 ; If Y1 = Y2 then the start and end points are the same, BEQ vlin3 ; so return from the subroutine (as vlin3 contains ; an RTS) BCC vlin1 ; If Y1 < Y2, jump to vlin1 to skip the following code, ; as (X1, Y1) is already the top point LDA Y2 ; Swap the values of Y1 and Y2, so we know that (X1, Y1) STA Y1 ; is at the top and (X1, Y2) is at the bottom STY Y2 TAY ; Set Y = Y1 once again .vlin1 LDA X1 ; Set SC2(1 0) = (nameBufferHi 0) + yLookup(Y) + X1 / 8 LSR A ; LSR A ; where yLookup(Y) uses the (yLookupHi yLookupLo) table LSR A ; to convert the pixel y-coordinate in Y into the number CLC ; of the first tile on the row containing the pixel ADC yLookupLo,Y ; STA SC2 ; Adding nameBufferHi and X1 / 8 therefore sets SC2(1 0) LDA nameBufferHi ; to the address of the entry in the nametable buffer ADC yLookupHi,Y ; that contains the tile number for the tile containing STA SC2+1 ; the pixel at (X1, Y), i.e. the line we are drawing LDA X1 ; Set S = X1 mod 8, which is the pixel column within the AND #7 ; character block at which we want to draw the start of STA S ; our line (as each character block has 8 columns) ; ; As we are drawing a vertical line, we do not need to ; vary the value of S, as we will always want to draw on ; the same pixel column within each character block LDA Y2 ; Set R = Y2 - Y1 SEC ; SBC Y1 ; So R is the height of the line we want to draw, which STA R ; we will use as a counter as we work our way along the ; line from top to bottom - in other words, R will the ; height remaining that we have to draw TYA ; Set Y = Y1 mod 8, which is the pixel row within the AND #7 ; character block at which we want to draw the start of TAY ; our line (as each character block has 8 rows) BNE vlin4 ; If Y is non-zero then our vertical line is starting ; inside a character block rather than from the very ; top, so jump to vlin4 to draw the top end of the line JMP vlin13 ; Otherwise jump to vlin13 to draw the middle part of ; the line from full-height line segments, as we don't ; need to draw a separate block for the top end
Name: DrawVerticalLine (Part 2 of 3) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw the top end or bottom end of the line Deep dive: Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.vlin2 ; If we get here then we need to move down by one ; character block without drawing anything, and then ; move on to drawing the middle portion of the line STY T ; Set A = R + Y LDA R ; = pixels to left draw + current pixel row ADC T ; ; So A contains the total number of pixels left to draw ; in our line SBC #7 ; At this point the C flag is clear, as the above ; addition won't overflow, so this sets A = R + Y - 8 ; and sets the flags accordingly BCC vlin3 ; If the above subtraction didn't underflow then ; R + Y < 8, so there is less than one block height to ; draw, so there would be nothing more to draw after ; moving down one line, so jump to vlin3 to return ; from the subroutine JMP vlin12 ; Jump to vlin12 to move on drawing the middle ; portion of the line .vlin3 LDY YSAV ; Restore Y from YSAV, so that it's preserved RTS ; Return from the subroutine .vlin4 ; We now draw either the top end or the bottom end of ; the line into the nametable entry in SC2(1 0) STY Q ; Set Q to the pixel row of the top of the line that we ; want to draw (which will be Y1 mod 8 for the top end ; of the line, or 0 for the bottom end of the line) ; ; For the top end of the line, we draw down from row ; Y1 mod 8 to the bottom of the character block, which ; will correctly draw the top end of the line ; ; For the bottom end of the line, we draw down from row ; 0 until R runs down, which will correctly draw the ; bottom end of the line SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; If the nametable buffer entry is non-zero for the tile LDA (SC2,X) ; containing the pixels that we want to draw, then a BNE vlin6 ; pattern has already been allocated to this entry, so ; skip the following LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ vlin5 ; patterns to use for drawing lines and pixels, so jump ; to vlin2 via vlin5 to move on to the next character ; block down, as we don't have enough patterns to draw ; the top block of the line STA (SC2,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; pattern to cover the pixels that we want to draw by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be used the ; next time we need to draw lines or pixels into a ; pattern JMP vlin8 ; Jump to vlin8 to draw the line, starting by drawing ; the top end into the newly allocated pattern number ; in A .vlin5 JMP vlin2 ; Jump to vlin2 to move on to the next character block ; down .vlin6 CMP #60 ; If A >= 60, then the pattern that's already allocated BCS vlin8 ; is one of the patterns we have reserved for drawing, ; so jump to vlin8 to draw the line, starting by drawing ; the top end into the pattern number in A CMP #37 ; If A < 37, then the pattern that's already allocated BCC vlin5 ; is one of the icon bar pattern, so jump to vlin2 via ; vlin5 to move down by one character block without ; drawing anything, as we can't draw on the icon bar ; If we get here then 37 <= A <= 59, so the pattern ; that's already allocated is one of the pre-rendered ; patterns containing horizontal and vertical lines ; ; We don't want to draw over the top of the pre-rendered ; patterns as that will break them, so instead we make a ; copy of the pre-rendered pattern into a newly ; allocated pattern, and then draw our line into the ; this new pattern, thus preserving what's already shown ; on-screen while still drawing our new line LDX pattBufferHiDiv8 ; Set SC3(1 0) = (pattBufferHiDiv8 A) * 8 STX SC3+1 ; = (pattBufferHi A) + A * 8 ASL A ; ROL SC3+1 ; So SC3(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC3+1 ; pattern data), which means SC3(1 0) points to the ASL A ; pattern data for the tile containing the pre-rendered ROL SC3+1 ; pattern that we want to copy STA SC3 LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ vlin5 ; patterns for drawing lines and pixels, so jump to ; vlin2 via vlin5 to move down by one character block ; without drawing anything, as we don't have enough ; patterns to draw the top end of the line LDX #0 ; Otherwise firstFreePattern contains the number of the STA (SC2,X) ; next available pattern for drawing, so allocate this ; pattern to cover the pixels that we want to copy by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be used the ; next time we need to draw lines or pixels into a ; pattern LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the pattern we just fetched ROL SC+1 STA SC ; We now have a new pattern in SC(1 0) into which we can ; draw the top end of our line, so we now need to copy ; the pre-rendered pattern that we want to draw on top ; of ; ; Each pattern is made up of eight bytes, so we simply ; need to copy eight bytes from SC3(1 0) to SC(1 0) STY T ; Store Y in T so we can retrieve it after the following ; loop LDY #7 ; We now copy eight bytes from SC3(1 0) to SC(1 0), so ; set a counter in Y .vlin7 LDA (SC3),Y ; Copy the Y-th byte of SC3(1 0) to the Y-th byte of STA (SC),Y ; SC(1 0) DEY ; Decrement the counter BPL vlin7 ; Loop back until we have copied all eight bytes LDY T ; Restore the value of Y from before the loop, so it ; once again contains the pixel row offset within the ; each character block for the line we are drawing JMP vlin9 ; Jump to hlin8 to draw the top end of the line into ; the tile that we just copied .vlin8 ; If we get here then we have either allocated a new ; pattern number for the line, or the pattern number ; already allocated to this part of the line is >= 60, ; which is a pattern into which we can draw ; ; In either case the pattern number is in A LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC .vlin9 LDX S ; Set X to the pixel column within the character block ; at which we want to draw our line, which we stored in ; S in part 1 LDY Q ; Set Y to y-coordinate of the start of the line, which ; we stored in Q above LDA R ; If the height remaining in R is 0 then we have no more BEQ vlin11 ; line to draw, so jump to vlin11 to return from the ; subroutine .vlin10 LDA (SC),Y ; Draw a pixel at x-coordinate X into the Y-th byte ORA TWOS,X ; of SC(1 0) STA (SC),Y DEC R ; Decrement the height remaining counter in R, as we ; just drew a pixel BEQ vlin11 ; If the height remaining in R is 0 then we have no more ; line to draw, so jump to vlin11 to return from the ; subroutine INY ; Increment the y-coordinate in Y so we move down the ; line by one pixel CPY #8 ; If Y < 8, loop back to vlin10 draw the next pixel as BCC vlin10 ; we haven't yet reached the bottom of the character ; block containing the line's top end BCS vlin12 ; If Y >= 8 then we have drawn our vertical line from ; the starting point to the bottom of the character ; block containing the line's top end, so jump to vlin12 ; to move down one row to draw the middle portion of the ; line .vlin11 LDY YSAV ; Restore Y from YSAV, so that it's preserved RTS ; Return from the subroutine
Name: DrawVerticalLine (Part 3 of 3) [Show more] Type: Subroutine Category: Drawing lines Summary: Draw the middle portion of the line from full-height blocks Deep dive: Drawing lines in the NES version
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.vlin12 ; We now draw the middle part of the line (i.e. the part ; between the top and bottom caps) SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDY #0 ; We want to start drawing the line from the top pixel ; line in the next character row, so set Y = 0 to use as ; the pixel row number ; Next, we update SC2(1 0) to the address of the next ; row down in the nametable buffer, which we can do by ; adding 32 as there are 32 tiles in each row LDA SC2 ; Set SC2(1 0) = SC2(1 0) + 32 CLC ; ADC #32 ; Starting with the low bytes STA SC2 BCC vlin13 ; And then the high bytes INC SC2+1 .vlin13 ; We jump here from part 2 if the line starts at the top ; of a character block LDA R ; If the height remaining in R is 0 then we have no more BEQ vlin11 ; line to draw, so jump to vlin11 to return from the ; subroutine SEC ; Set A = A - 8 SBC #8 ; = R - 8 ; ; So this subtracts 8 pixels (one block) from the number ; of pixels we still have to draw BCS vlin14 ; If the subtraction didn't underflow, then there are at ; least 8 more pixels to draw, so jump to vlin14 to draw ; another block's worth of pixels JMP vlin4 ; The subtraction underflowed, so R is less than 8 and ; we need to stop drawing full-height blocks and draw ; the bottom end of the line, so jump to vlin4 with ; Y = 0 to do just this .vlin14 STA R ; Store the updated number of pixels left to draw, which ; we calculated in the subtraction above LDX #0 ; If the nametable buffer entry is zero for the tile LDA (SC2,X) ; containing the pixels that we want to draw, then a BEQ vlin15 ; pattern has not yet been allocated to this entry, so ; jump to vlin15 to place a pre-rendered pattern into ; the nametable entry CMP #60 ; If A < 60, then the pattern that's already allocated BCC vlin17 ; is either an icon bar pattern, or one of the ; pre-rendered patterns containing horizontal and ; vertical lines, so jump to vlin17 to process drawing ; on top of the pre-rendered pattern ; If we get here then the pattern number already ; allocated to this part of the line is >= 60, which is ; a pattern into which we can draw ; ; The pattern number is in A LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC LDX S ; Set X to the pixel column within the character block ; at which we want to draw our line, which we stored in ; S in part 1 LDY #0 ; We are going to draw a vertical line from pixel row 0 ; to row 7, so set an index in Y to count up ; We repeat the following code eight times, so it draws ; eight pixels from the top of the character block to ; the bottom LDA (SC),Y ; Draw a pixel at x-coordinate X into the Y-th byte ORA TWOS,X ; of SC(1 0) STA (SC),Y INY ; Increment the index in Y LDA (SC),Y ; Draw a pixel at x-coordinate X into the Y-th byte ORA TWOS,X ; of SC(1 0) STA (SC),Y INY ; Increment the index in Y LDA (SC),Y ; Draw a pixel at x-coordinate X into the Y-th byte ORA TWOS,X ; of SC(1 0) STA (SC),Y INY ; Increment the index in Y LDA (SC),Y ; Draw a pixel at x-coordinate X into the Y-th byte ORA TWOS,X ; of SC(1 0) STA (SC),Y INY ; Increment the index in Y LDA (SC),Y ; Draw a pixel at x-coordinate X into the Y-th byte ORA TWOS,X ; of SC(1 0) STA (SC),Y INY ; Increment the index in Y LDA (SC),Y ; Draw a pixel at x-coordinate X into the Y-th byte ORA TWOS,X ; of SC(1 0) STA (SC),Y INY ; Increment the index in Y LDA (SC),Y ; Draw a pixel at x-coordinate X into the Y-th byte ORA TWOS,X ; of SC(1 0) STA (SC),Y INY ; Increment the index in Y LDA (SC),Y ; Draw a pixel at x-coordinate X into the Y-th byte ORA TWOS,X ; of SC(1 0) STA (SC),Y JMP vlin12 ; Loop back to move down a row and draw the next block .vlin15 LDA S ; Set A to the pixel column within the character block ; at which we want to draw our line, which we stored in ; S in part 1 CLC ; Patterns 52 to 59 contain pre-rendered patterns as ADC #52 ; follows: ; ; * Pattern 52 has a vertical line in pixel column 0 ; * Pattern 53 has a vertical line in pixel column 1 ; ... ; * Pattern 58 has a vertical line in pixel column 6 ; * Pattern 59 has a vertical line in pixel column 7 ; ; So A contains the pre-rendered pattern number that ; contains an 8-pixel line in pixel column S, and as S ; contains the offset of the pixel column for the line ; we are drawing, this means A contains the correct ; pattern number for this part of the line STA (SC2,X) ; Display the pre-rendered pattern on-screen by setting ; the nametable entry to A .vlin16 JMP vlin12 ; Loop back to move down a row and draw the next block .vlin17 ; If we get here then A <= 59, so the pattern that's ; already allocated is either an icon bar pattern, or ; one of the pre-rendered patterns containing horizontal ; and vertical lines ; ; We jump here with X = 0, so the write to (SC2,X) ; below will work properly STA SC ; Set SC to the number of the pattern that is already ; allocated to this part of the screen, so we can ; retrieve it later LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ vlin16 ; patterns to use for drawing lines and pixels, so jump ; to vlin16 to move down by one character block without ; drawing anything, as we don't have enough patterns to ; draw this part of the line INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be used the ; next time we need to draw lines or pixels into a ; pattern STA (SC2,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; tile to contain the pre-rendered pattern that we want ; to copy by setting the nametable entry to the pattern ; number we just fetched LDX pattBufferHiDiv8 ; Set SC3(1 0) = (pattBufferHiDiv8 A) * 8 STX SC3+1 ; = (pattBufferHi A) + A * 8 ASL A ; ROL SC3+1 ; So SC3(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC3+1 ; pattern data), which means SC3(1 0) points to the ASL A ; pattern data for the pattern we just fetched ROL SC3+1 STA SC3 LDA SC ; Set A to the number of the pattern that is already ; allocated to this part of the screen, which we stored ; in SC above LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the pre-rendered ROL SC+1 ; pattern that we want to copy STA SC ; We now have a new pattern in SC3(1 0) into which we ; can draw the middle part of our line, so we now need ; to copy the pattern of the pre-rendered pattern that ; we want to draw on top of ; ; Each pattern is made up of eight bytes, so we simply ; need to copy eight bytes from SC(1 0) to SC3(1 0) STY T ; Store Y in T so we can retrieve it after the following ; loop LDY #7 ; We now copy eight bytes from SC(1 0) to SC3(1 0), so ; set a counter in Y LDX S ; Set X to the pixel column within the character block ; at which we want to draw our line, which we stored in ; S in part 1 .vlin18 LDA (SC),Y ; Draw a pixel at x-coordinate X into the Y-th byte ORA TWOS,X ; of SC(1 0) and store the result in the Y-th byte of STA (SC3),Y ; SC3(1 0), so this copies the pre-rendered pattern, ; superimposes our vertical line on the result and ; stores it in the pattern buffer for the tile we just ; allocated DEY ; Decrement the counter BPL vlin18 ; Loop back until we have copied all eight bytes BMI vlin16 ; Jump to vlin12 via vlin16 to move down a row and draw ; the next block
Name: PIXEL [Show more] Type: Subroutine Category: Drawing pixels Summary: Draw a 1-pixel dot Deep dive: Drawing pixels in the NES version
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOEXP calls PIXEL * DrawExplosionBurst calls PIXEL * DrawDash calls via pixl2

This routine does a similar job to the routine of the same name in the BBC Master version of Elite, but the code is significantly different.
Arguments: X The screen x-coordinate of the point to draw A The screen y-coordinate of the point to draw
Returns: Y Y is preserved
Other entry points: pixl2 Restore the value of Y and return from the subroutine
.PIXEL STX SC2 ; Set SC2 to the pixel's x-coordinate in X STY T1 ; Store Y in T1 so we can retrieve it at the end of the ; subroutine TAY ; Set Y to the pixel's y-coordinate TXA ; Set SC(1 0) = (nameBufferHi 0) + yLookup(Y) + X / 8 LSR A ; LSR A ; where yLookup(Y) uses the (yLookupHi yLookupLo) table LSR A ; to convert the pixel y-coordinate in Y into the number CLC ; of the first tile on the row containing the pixel ADC yLookupLo,Y ; STA SC ; Adding nameBufferHi and X / 8 therefore sets SC(1 0) LDA nameBufferHi ; to the address of the entry in the nametable buffer ADC yLookupHi,Y ; that contains the tile number for the tile containing STA SC+1 ; the pixel at (X, Y), i.e. the line we are drawing SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; If the nametable buffer entry is non-zero for the tile LDA (SC,X) ; containing the pixel that we want to draw, then a BNE pixl1 ; pattern has already been allocated to this entry, so ; skip the following LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ pixl2 ; patterns to use for drawing lines and pixels, so jump ; to pixl2 to return from the subroutine, as we can't ; draw the pixel STA (SC,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; pattern to cover the pixel that we want to draw by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be added to ; the nametable the next time we need to draw lines or ; pixels into a pattern .pixl1 LDX pattBufferHiDiv8 ; Set SC(1 0) = (pattBufferHiDiv8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC TYA ; Set Y = Y mod 8, which is the pixel row within the AND #7 ; character block at which we want to draw the start of TAY ; our line (as each character block has 8 rows) LDA SC2 ; Set X = X mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide, and we set SC2 to the x-coordinate above) LDA TWOS,X ; Fetch a 1-pixel byte from TWOS where pixel X is set ORA (SC),Y ; Store the pixel byte into screen memory at SC(1 0), STA (SC),Y ; using OR logic so it merges with whatever is already ; on-screen .pixl2 LDY T1 ; Restore the value of Y from T1 so it is preserved RTS ; Return from the subroutine
Name: DrawDash [Show more] Type: Subroutine Category: Drawing pixels Summary: Draw a 2-pixel dash Deep dive: Drawing pixels in the NES version
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT22 calls DrawDash

Arguments: X The screen x-coordinate of the dash to draw A The screen y-coordinate of the dash to draw
Returns: Y Y is preserved
.DrawDash STX SC2 ; Set SC2 to the pixel's x-coordinate in X STY T1 ; Store Y in T1 so we can retrieve it at the end of the ; subroutine TAY ; Set Y to the pixel's y-coordinate TXA ; Set SC(1 0) = (nameBufferHi 0) + yLookup(Y) + X / 8 LSR A ; LSR A ; where yLookup(Y) uses the (yLookupHi yLookupLo) table LSR A ; to convert the pixel y-coordinate in Y into the number CLC ; of the first tile on the row containing the pixel ADC yLookupLo,Y ; STA SC ; Adding nameBufferHi and X / 8 therefore sets SC(1 0) LDA nameBufferHi ; to the address of the entry in the nametable buffer ADC yLookupHi,Y ; that contains the tile number for the tile containing STA SC+1 ; the pixel at (X, Y), i.e. the line we are drawing SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #0 ; If the nametable buffer entry is non-zero for the tile LDA (SC,X) ; containing the pixel that we want to draw, then a BNE dash1 ; pattern has already been allocated to this entry, so ; skip the following LDA firstFreePattern ; If firstFreePattern is zero then we have run out of BEQ pixl2 ; patterns to use for drawing lines and pixels, so jump ; to pixl2 to return from the subroutine, as we can't ; draw the dash STA (SC,X) ; Otherwise firstFreePattern contains the number of the ; next available pattern for drawing, so allocate this ; pattern to cover the dash that we want to draw by ; setting the nametable entry to the pattern number we ; just fetched INC firstFreePattern ; Increment firstFreePattern to point to the next ; available pattern for drawing, so it can be added to ; the nametable the next time we need to draw lines or ; pixels into a pattern .dash1 LDX #HI(pattBuffer0)/8 ; Set SC(1 0) = (pattBuffer0/8 A) * 8 STX SC+1 ; = (pattBufferHi 0) + A * 8 ASL A ; ROL SC+1 ; So SC(1 0) is the address in the pattern buffer for ASL A ; pattern number A (as each pattern contains 8 bytes of ROL SC+1 ; pattern data), which means SC(1 0) points to the ASL A ; pattern data for the tile containing the line we are ROL SC+1 ; drawing STA SC TYA ; Set Y = Y mod 8, which is the pixel row within the AND #7 ; character block at which we want to draw the start of TAY ; our line (as each character block has 8 rows) LDA SC2 ; Set X = X mod 8, which is the horizontal pixel number AND #7 ; within the character block where the line starts (as TAX ; each pixel line in the character block is 8 pixels ; wide, and we set SC2 to the x-coordinate above) LDA TWOS2,X ; Fetch a 2-pixel byte from TWOS2 where pixels X and X+1 ; are set ORA (SC),Y ; Store the dash byte into screen memory at SC(1 0), STA (SC),Y ; using OR logic so it merges with whatever is already ; on-screen LDY T1 ; Restore the value of Y from T1 so it is preserved RTS ; Return from the subroutine
Name: ECBLB2 [Show more] Type: Subroutine Category: Dashboard Summary: Start up the E.C.M. (start the countdown and make the E.C.M. sound)
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 3 of 16) calls ECBLB2 * TACTICS (Part 1 of 7) calls ECBLB2
.ECBLB2 LDA #32 ; Set the E.C.M. countdown timer in ECMA to 32 STA ECMA LDY #2 ; Call the NOISE routine with Y = 2 to make the sound JMP NOISE ; of the E.C.M., returning from the subroutine using a ; tail call
Name: MSBAR [Show more] Type: Subroutine Category: Dashboard Summary: Draw a specific indicator in the dashboard's missile bar
Context: See this subroutine on its own page References: This subroutine is called as follows: * ABORT2 calls MSBAR * Main flight loop (Part 3 of 16) calls MSBAR * msblob calls MSBAR

Arguments: X The number of the missile indicator to update (counting from bottom-right to bottom-left, then top-left and top-right, so indicator NOMSL is the top-right indicator) Y The pattern number for the new missile indicator: * 133 = no missile indicator * 109 = red (armed and locked) * 108 = black (disarmed) The armed missile flashes black and red, so the tile is swapped between 108 and 109 in the main loop
Returns: X X is preserved Y Y is set to 0
.MSBAR TYA ; Store the pattern number on the stack so we can PHA ; retrieve it later LDY missileNames,X ; Set Y to the X-th entry from the missileNames table, ; so Y is the offset of missile X's indicator in the ; nametable buffer, from the start of row 22 PLA ; Set the nametable buffer entry to the pattern number STA nameBuffer0+22*32,Y LDY #0 ; Set Y = 0 to return from the subroutine (so this ; routine behaves like the same routine in the other ; versions of Elite) RTS ; Return from the subroutine
Name: missileNames [Show more] Type: Variable Category: Dashboard Summary: Tile numbers for the four missile indicators on the dashboard, as offsets from the start of tile row 22
Context: See this variable on its own page References: This variable is used as follows: * MSBAR uses missileNames

The active missile (i.e. the one that is armed and fired first) is the one with the highest number, so missile 4 (top-left) will be armed before missile 3 (top-right), and so on.
.missileNames EQUB 0 ; Missile numbers are from 1 to 4, so this value is ; never used EQUB 95 ; Missile 1 (bottom-right) EQUB 94 ; Missile 2 (bottom-left) EQUB 63 ; Missile 3 (top-right) EQUB 62 ; Missile 4 (top-left)
Name: autoPlayKeys1_EN [Show more] Type: Variable Category: Combat demo Summary: Auto-play commands for the first part of the auto-play combat demo (combat practice) when English is the chosen language Deep dive: Multi-language support in NES Elite Auto-playing the combat demo
Context: See this variable on its own page References: This variable is used as follows: * autoPlayKeys1Hi uses autoPlayKeys1_EN * autoPlayKeys1Lo uses autoPlayKeys1_EN
.autoPlayKeys1_EN ; At this point the we are at the title screen, which ; will show the rotating Cobra Mk III before starting ; the combat demo in auto-play mode EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks ; At this point the combat demo starts EQUB $C2 ; Do nothing (%00000000) while MANY+19 = 0 (i.e. wait EQUB %00000000 ; until the Sidewinder - ship type 19 - is spawned) EQUW MANY+19 EQUB $8A ; Do nothing for 10 * 4 = 40 VBlanks EQUB %01000000 ; Press the A button (%01000000) for 4 VBlanks to fire EQUB 4 ; the laser (this kills the first ship) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB $C2 ; Do nothing (%00000000) while FRIN+4 = 0 (i.e. wait EQUB %00000000 ; until the third ship is spawned in ship slot 4) EQUW FRIN+4 EQUB $9C ; Do nothing for 12 * 4 = 48 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 20 VBlanks to EQUB 20 ; pull the nose up EQUB %01000100 ; Press the down and A buttons (%01000100) for 6 VBlanks EQUB 6 ; to pull the nose up and fire the lasers EQUB %01000000 ; Press the A button (%01000000) for 31 VBlanks to fire EQUB 31 ; the lasers EQUB %01000000 ; Press the A button (%01000000) for 31 VBlanks to fire EQUB 31 ; the lasers EQUB %00100001 ; Press the right and B buttons (%01000100) for 14 EQUB 14 ; VBlanks to move the icon bar pointer to the right ; and onto the Target Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. target the missile) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $8D ; Do nothing for 13 * 4 = 52 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 31 VBlanks to EQUB 31 ; roll to the right (clockwise) EQUB %00000001 ; EN/FR: Press the right button (%00000001) for 21 EQUB 21 ; VBlanks to roll to the right (clockwise) EQUB %00001000 ; Press the up button (%00001000) for 20 VBlanks to EQUB 20 ; pitch down EQUB $8E ; Do nothing for 14 * 4 = 56 VBlanks EQUB %00001000 ; Press the up button (%00001000) for 31 VBlanks to EQUB 31 ; pitch down EQUB %00001000 ; EN: Press the up button (%00001000) for 20 VBlanks to EQUB 20 ; pitch down EQUB %00001000 ; EN: Press the up button (%00001000) for 20 VBlanks to EQUB 20 ; pitch down EQUB %00100001 ; Press the right and B buttons (%00100001) for 2 EQUB 2 ; VBlanks to move the icon bar pointer to the right ; and onto the Fire Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB $C3 ; Press the up button (%00001000) while bit 7 of MSTG is EQUB %00001000 ; set (i.e. pull up until the missile has locked onto a EQUW MSTG ; target) EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. fire the missile), ; which sends a missile to kill the second ship EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; EN: Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; EN: Do nothing for 31 * 4 = 124 VBlanks EQUB %00100010 ; Press the left and B buttons (%00100010) for 22 EQUB 22 ; VBlanks to move the icon bar pointer to the left ; and onto the Front View button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. to change from front ; view to rear view) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 18 EQUB 18 ; VBlanks to move the icon bar pointer to the right ; and onto the Target Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; EN: Press the right button (%00000001) for 8 VBlanks EQUB 8 ; to roll to the right (clockwise) EQUB %00000100 ; EN: Press the down button (%00000100) for 31 VBlanks EQUB 31 ; to pull the nose up EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. target the missile) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 2 EQUB 2 ; VBlanks to move the icon bar pointer to the right ; and onto the Fire Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; EN: Press the down button (%00000100) for 19 VBlanks EQUB 19 ; to pull the nose up EQUB %00100100 ; EN: Press the down and B buttons (%00100100) for 17 EQUB 17 ; VBlanks to reduce our speed EQUB $C3 ; Do nothing (%00000000) while bit 7 of MSTG is set EQUB %00000000 ; (i.e. do nothing until the missile has locked onto a EQUW MSTG ; target) EQUB $C0 ; Switch to the autoPlayKeys2 table in the next VBlank ; to move on to the second part of the auto-play demo, ; which demonstrates the game itself
Name: autoPlayKeys1_DE [Show more] Type: Variable Category: Combat demo Summary: Auto-play commands for the first part of the auto-play combat demo (combat practice) when German is the chosen language Deep dive: Multi-language support in NES Elite Auto-playing the combat demo
Context: See this variable on its own page References: This variable is used as follows: * autoPlayKeys1Hi uses autoPlayKeys1_DE * autoPlayKeys1Lo uses autoPlayKeys1_DE
.autoPlayKeys1_DE ; At this point the we are at the title screen, which ; will show the rotating Cobra Mk III before starting ; the combat demo in auto-play mode EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks ; At this point the combat demo starts EQUB $C2 ; Do nothing (%00000000) while MANY+19 = 0 (i.e. wait EQUB %00000000 ; until the Sidewinder - ship type 19 - is spawned) EQUW MANY+19 EQUB $8A ; Do nothing for 10 * 4 = 40 VBlanks EQUB %01000000 ; Press the A button (%01000000) for 4 VBlanks to fire EQUB 4 ; the laser (this kills the first ship) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB $C2 ; Do nothing (%00000000) while FRIN+4 = 0 (i.e. wait EQUB %00000000 ; until the third ship is spawned in ship slot 4) EQUW FRIN+4 EQUB $9C ; Do nothing for 12 * 4 = 48 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 20 VBlanks to EQUB 20 ; pull the nose up EQUB %01000100 ; Press the down and A buttons (%01000100) for 6 VBlanks EQUB 6 ; to pull the nose up and fire the lasers EQUB %01000000 ; Press the A button (%01000000) for 31 VBlanks to fire EQUB 31 ; the lasers EQUB %01000000 ; Press the A button (%01000000) for 31 VBlanks to fire EQUB 31 ; the lasers EQUB %00100001 ; Press the right and B buttons (%01000100) for 14 EQUB 14 ; VBlanks to move the icon bar pointer to the right ; and onto the Target Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. target the missile) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $8D ; Do nothing for 13 * 4 = 52 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 31 VBlanks to EQUB 31 ; roll to the right (clockwise) EQUB %00000001 ; DE: Press the right button (%00000001) for 19 VBlanks EQUB 19 ; to roll to the right (clockwise) EQUB %00001000 ; Press the up button (%00001000) for 20 VBlanks to EQUB 20 ; pitch down EQUB $8E ; Do nothing for 14 * 4 = 56 VBlanks EQUB %00001000 ; Press the up button (%00001000) for 31 VBlanks to EQUB 31 ; pitch down EQUB %00001000 ; DE: Press the up button (%00001000) for 31 VBlanks to EQUB 31 ; pitch down EQUB %00001000 ; DE: Press the up button (%00001000) for 22 VBlanks to EQUB 22 ; pitch down EQUB %00100001 ; Press the right and B buttons (%00100001) for 2 EQUB 2 ; VBlanks to move the icon bar pointer to the right ; and onto the Fire Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB $C3 ; Press the up button (%00001000) while bit 7 of MSTG is EQUB %00001000 ; set (i.e. pull up until the missile has locked onto a EQUW MSTG ; target) EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. fire the missile), ; which sends a missile to kill the second ship EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; DE: Do nothing for 31 * 4 = 124 VBlanks EQUB %00100010 ; Press the left and B buttons (%00100010) for 22 EQUB 22 ; VBlanks to move the icon bar pointer to the left ; and onto the Front View button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. to change from front ; view to rear view) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 18 EQUB 18 ; VBlanks to move the icon bar pointer to the right ; and onto the Target Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. target the missile) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 2 EQUB 2 ; VBlanks to move the icon bar pointer to the right ; and onto the Fire Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; DE: Press the right button (%00000001) for 12 VBlanks EQUB 12 ; to roll to the right (clockwise) EQUB %00000100 ; DE: Press the down button (%00000100) for 31 VBlanks EQUB 31 ; to pull the nose up EQUB %00000100 ; DE: Press the down button (%00000100) for 30 VBlanks EQUB 30 ; to pull the nose up EQUB %00100100 ; DE: Press the down and B buttons (%00100100) for 22 EQUB 22 ; VBlanks to reduce our speed EQUB $C3 ; Do nothing (%00000000) while bit 7 of MSTG is set EQUB %00000000 ; (i.e. do nothing until the missile has locked onto a EQUW MSTG ; target) EQUB $C0 ; Switch to the autoPlayKeys2 table in the next VBlank ; to move on to the second part of the auto-play demo, ; which demonstrates the game itself
Name: autoPlayKeys1_FR [Show more] Type: Variable Category: Combat demo Summary: Auto-play commands for the first part of the auto-play combat demo (combat practice) when French is the chosen language Deep dive: Multi-language support in NES Elite Auto-playing the combat demo
Context: See this variable on its own page References: This variable is used as follows: * autoPlayKeys1Hi uses autoPlayKeys1_FR * autoPlayKeys1Lo uses autoPlayKeys1_FR
.autoPlayKeys1_FR ; At this point the we are at the title screen, which ; will show the rotating Cobra Mk III before starting ; the combat demo in auto-play mode EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks ; At this point the combat demo starts EQUB $C2 ; Do nothing (%00000000) while MANY+19 = 0 (i.e. wait EQUB %00000000 ; until the Sidewinder - ship type 19 - is spawned) EQUW MANY+19 EQUB $8A ; Do nothing for 10 * 4 = 40 VBlanks EQUB %01000000 ; Press the A button (%01000000) for 4 VBlanks to fire EQUB 4 ; the laser (this kills the first ship) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB $C2 ; Do nothing (%00000000) while FRIN+4 = 0 (i.e. wait EQUB %00000000 ; until the third ship is spawned in ship slot 4) EQUW FRIN+4 EQUB $9C ; Do nothing for 12 * 4 = 48 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 20 VBlanks to EQUB 20 ; pull the nose up EQUB %01000100 ; Press the down and A buttons (%01000100) for 6 VBlanks EQUB 6 ; to pull the nose up and fire the lasers EQUB %01000000 ; Press the A button (%01000000) for 31 VBlanks to fire EQUB 31 ; the lasers EQUB %01000000 ; Press the A button (%01000000) for 31 VBlanks to fire EQUB 31 ; the lasers EQUB %00100001 ; Press the right and B buttons (%01000100) for 14 EQUB 14 ; VBlanks to move the icon bar pointer to the right ; and onto the Target Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. target the missile) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $8D ; Do nothing for 13 * 4 = 52 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 31 VBlanks to EQUB 31 ; roll to the right (clockwise) EQUB %00000001 ; FR/EN: Press the right button (%00000001) for 21 EQUB 21 ; VBlanks to roll to the right (clockwise) EQUB %00001000 ; Press the up button (%00001000) for 20 VBlanks to EQUB 20 ; pitch down EQUB $8E ; Do nothing for 14 * 4 = 56 VBlanks EQUB %00001000 ; Press the up button (%00001000) for 31 VBlanks to EQUB 31 ; pitch down EQUB %00001000 ; FR: Press the up button (%00001000) for 31 VBlanks to EQUB 31 ; pitch down EQUB %00001000 ; FR: Press the up button (%00001000) for 20 VBlanks to EQUB 20 ; pitch down EQUB %00100001 ; Press the right and B buttons (%00100001) for 2 EQUB 2 ; VBlanks to move the icon bar pointer to the right EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks ; and onto the Fire Missile button EQUB $C3 ; Press the up button (%00001000) while bit 7 of MSTG is EQUB %00001000 ; set (i.e. pull up until the missile has locked onto a EQUW MSTG ; target) EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. fire the missile), ; which sends a missile to kill the second ship EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; FR: Do nothing for 31 * 4 = 124 VBlanks EQUB $98 ; FR: Do nothing for 24 * 4 = 96 VBlanks EQUB %00100010 ; Press the left and B buttons (%00100010) for 22 EQUB 22 ; VBlanks to move the icon bar pointer to the left ; and onto the Front View button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. to change from front ; view to rear view) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 18 EQUB 18 ; VBlanks to move the icon bar pointer to the right ; and onto the Target Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. target the missile) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 2 EQUB 2 ; VBlanks to move the icon bar pointer to the right ; and onto the Fire Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; FR: Press the right button (%00000001) for 14 VBlanks EQUB 14 ; to roll to the right (clockwise) EQUB %00000100 ; FR: Press the down button (%00000100) for 31 VBlanks EQUB 31 ; to pull the nose up EQUB %00100100 ; FR: Press the down and B buttons (%00100100) for 17 EQUB 17 ; VBlanks to reduce our speed EQUB %00000100 ; FR: Press the down button (%00000100) for 28 VBlanks EQUB 28 ; to pull the nose up EQUB $C3 ; Do nothing (%00000000) while bit 7 of MSTG is set EQUB %00000000 ; (i.e. do nothing until the missile has locked onto a EQUW MSTG ; target) ; Fall through into the autoPlayKeys2 table to move on ; to the second part of the auto-play demo, which ; demonstrates the game itself
Name: autoPlayKeys2 [Show more] Type: Variable Category: Combat demo Summary: Auto-play commands for the second part of the auto-play demo (demonstrating the game itself) Deep dive: Auto-playing the combat demo
Context: See this variable on its own page References: This variable is used as follows: * AutoPlayDemo uses autoPlayKeys2
.autoPlayKeys2 EQUB $89 ; Do nothing for 9 * 4 = 36 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. fire the missile), ; which sends a missile to kill the third ship EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00101000 ; Press the up and B buttons (%00101000) for 25 EQUB 25 ; VBlanks to increase our speed EQUB $C2 ; Do nothing (%00000000) while QQ12 = 0 (i.e. wait until EQUB %00000000 ; we are docked, which will happen when the combat demo EQUW QQ12 ; finishes after killing the third ship and showing the ; scroll text with the results of combat practice ; At this point the combat demo has finished and we are ; back at the title screen EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00100010 ; Press the left and B buttons (%00100010) for 22 EQUB 22 ; VBlanks to move the icon bar pointer to the left ; and onto the Equip Ship button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. Equip Ship) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00000100 ; FR: Press the down button (%00000100) for 4 VBlanks EQUB 4 ; to move the cursor down the list of equipment by one ; row onto the missile entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %01000000 ; Press the A button (%01000000) for 4 VBlanks to buy a EQUB 4 ; missile EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00100010 ; Press the left and B buttons (%00100010) for 18 EQUB 18 ; VBlanks to move the icon bar pointer to the left ; and onto the Market Price button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. Market Price) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 4 VBlanks to EQUB 4 ; buy one tonne of Food EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 4 VBlanks to EQUB 4 ; buy one tonne of Food (so we now have two) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 4 VBlanks to EQUB 4 ; buy one tonne of Food (so we now have three) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 4 VBlanks to EQUB 4 ; buy one tonne of Food (so we now have four) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 4 VBlanks to EQUB 4 ; buy one tonne of Food (so we now have five) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 4 VBlanks to EQUB 4 ; buy one tonne of Food (so we now have six) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 4 VBlanks to EQUB 4 ; buy one tonne of Food (so we now have seven) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 4 VBlanks to EQUB 4 ; buy one tonne of Food (so we now have eight) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Textiles ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 4 VBlanks to EQUB 4 ; buy one tonne of Textiles EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Radioactives ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Robot Slaves ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Beverages ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Luxuries ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Rare Species ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Computers ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Machinery ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Alloys ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Firearms ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Furs ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Minerals ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Gold ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Platinum ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Gem-stones ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 4 VBlanks to EQUB 4 ; buy one gram of Gem-stones EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. Inventory) to show the ; cargo that we just bought EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00100010 ; Press the left and B buttons (%00100010) for 2 EQUB 2 ; VBlanks to move the icon bar pointer to the left ; and onto the Launch button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. Launch) to launch from ; the space station ; At this point we are back in space, outside the space ; station at Lave EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 22 EQUB 22 ; VBlanks to move the icon bar pointer to the right ; and onto the Front View button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. to change from front ; view to rear view) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00001000 ; Press the up button (%00001000) for 30 VBlanks to EQUB 30 ; pitch down EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00100010 ; Press the left and B buttons (%00100010) for 2 EQUB 2 ; VBlanks to move the icon bar pointer to the left ; and onto the Charts button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. the Short-range Chart) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. System Data) to show ; the system data for Lave EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. the Short-range Chart) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00000001 ; Press the right button (%00000001) for 31 VBlanks to EQUB 31 ; move the crosshairs to the right EQUB %00000101 ; Press the right and down buttons (%00000101) for 31 EQUB 31 ; VBlanks to move the crosshairs down and to the right EQUB %00000001 ; Press the right button (%00000001) for 5 VBlanks to EQUB 5 ; move the crosshairs to the right, so Zaonce gets ; selected EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. System Data) to show ; the system data for Zaonce EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. the Short-range Chart) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00100010 ; Press the left and B buttons (%00100010) for 2 EQUB 2 ; VBlanks to move the icon bar pointer to the left ; and onto the Charts icon EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. the Long-range Chart) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. the Short-range Chart) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 26 EQUB 26 ; VBlanks to move the icon bar pointer to the right ; and onto the Hyperspace button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. hyperspace) to start ; the hyperspace countdown EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $96 ; Do nothing for 22 * 4 = 88 VBlanks EQUB %00100010 ; Press the left and B buttons (%00100010) for 18 EQUB 18 ; VBlanks to move the icon bar pointer to the left ; and onto the Front View button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. the front view) so we ; can see Lave in front of us EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $C4 ; Do nothing (%00000000) while bit 7 of FRIN+1 is clear EQUB %00000000 ; (i.e. do nothing until the sun has been spawned in EQUW FRIN+1 ; the second ship slot, at which point we know we have ; arrived in Zaonce) EQUB %00000010 ; Press the left button (%00000010) for 22 VBlanks to EQUB 22 ; roll to the left (anti-clockwise) EQUB %00000100 ; Press the down button (%00000100) for 30 VBlanks to EQUB 30 ; pull the nose up EQUB %00100001 ; Press the right and B buttons (%01000100) for 14 EQUB 34 ; VBlanks to move the icon bar pointer to the right ; and onto the Fast-forward button (for an in-system ; jump) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. do an in-system jump) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. do a second in-system ; jump) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. do a third in-system ; jump) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. do a fourth in-system ; jump) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $C2 ; Do nothing (%00000000) while MANY+2 = 0 (i.e. wait EQUB %00000000 ; until the space station - ship type 2 - is spawned) EQUW MANY+2 EQUB %00100010 ; Press the left and B buttons (%00100010) for 22 EQUB 58 ; VBlanks to move the icon bar pointer to the left ; and onto the Docking Computer button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. engage the docking ; computer) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $C2 ; Do nothing (%00000000) while QQ12 = 0 (i.e. wait until EQUB %00000000 ; we are docked) EQUW QQ12 ; At this point we are docked in Zaonce EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00100001 ; Press the right and B buttons (%01000100) for 2 EQUB 2 ; VBlanks to move the icon bar pointer to the right ; and onto the Market Price button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. Market Price) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00000010 ; Press the left button (%00000010) for 4 VBlanks to EQUB 4 ; sell one tonne of Food (so we now have seven tonnes ; left) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000010 ; Press the left button (%00000010) for 4 VBlanks to EQUB 4 ; sell one tonne of Food (so we now have six tonnes ; left) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000010 ; Press the left button (%00000010) for 4 VBlanks to EQUB 4 ; sell one tonne of Food (so we now have five tonnes ; left) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000010 ; Press the left button (%00000010) for 4 VBlanks to EQUB 4 ; sell one tonne of Food (so we now have four tonnes ; left) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000010 ; Press the left button (%00000010) for 4 VBlanks to EQUB 4 ; sell one tonne of Food (so we now have three tonnes ; left) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000010 ; Press the left button (%00000010) for 4 VBlanks to EQUB 4 ; sell one tonne of Food (so we now have two tonnes ; left) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000010 ; Press the left button (%00000010) for 4 VBlanks to EQUB 4 ; sell one tonne of Food (so we now have one tonne ; left) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000010 ; Press the left button (%00000010) for 4 VBlanks to EQUB 4 ; sell one tonne of Food (so we have sold them all) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000100 ; Press the down button (%00000100) for 4 VBlanks to EQUB 4 ; move the highlight down one row onto the Textiles ; entry EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00000010 ; Press the left button (%00000010) for 4 VBlanks to EQUB 4 ; sell one tonne of Textiles (so we have sold them all) EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 18 EQUB 18 ; VBlanks to move the icon bar pointer to the right ; and onto the Equip Ship button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. Equip Ship) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %01000000 ; Press the A button (%01000000) for 31 VBlanks to buy EQUB 31 ; fuel EQUB %01000000 ; Press the A button (%01000000) for 31 VBlanks to buy EQUB 31 ; fuel EQUB %01000000 ; Press the A button (%01000000) for 31 VBlanks to buy EQUB 31 ; fuel EQUB %01000000 ; Press the A button (%01000000) for 31 VBlanks to buy EQUB 31 ; fuel EQUB %00100010 ; Press the left and B buttons (%00100010) for 54 EQUB 54 ; VBlanks to move the icon bar pointer to the left ; and onto the Launch button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. Launch) to launch from ; the space station ; At this point we are back in space, outside the space ; station at Zaonce EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00001000 ; Press the up button (%00001000) for 31 VBlanks to EQUB 31 ; pitch down EQUB %00001000 ; Press the up button (%00001000) for 31 VBlanks to EQUB 31 ; pitch down EQUB %00101000 ; Press the up and B buttons (%00101000) for 10 EQUB 10 ; VBlanks to increase our speed EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 14 EQUB 14 ; VBlanks to move the icon bar pointer to the right ; and onto the Status Mode button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. Status Mode) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 14 EQUB 14 ; VBlanks to move the icon bar pointer to the right ; and onto the Front View button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. the front view) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 18 EQUB 18 ; VBlanks to move the icon bar pointer to the right ; and onto the Target Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00100100 ; Press the down and B buttons (%00100100) for 31 EQUB 31 ; VBlanks to reduce our speed EQUB %00001000 ; Press the up button (%00001000) for 31 VBlanks to EQUB 31 ; pitch down EQUB %00001000 ; Press the up button (%00001000) for 31 VBlanks to EQUB 31 ; pitch down EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. target the missile) EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $C3 ; Press the up button (%00001000) while bit 7 of MSTG is EQUB %00001000 ; set (i.e. pull up until the missile has locked onto EQUW MSTG ; the space station) EQUB $9F ; Do nothing for 31 * 4 = 124 VBlanks EQUB %00100001 ; Press the right and B buttons (%00100001) for 2 EQUB 2 ; VBlanks to move the icon bar pointer to the right ; and onto the Fire Missile button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. fire the missile), ; which fires a missile at the space station, triggering ; the station's E.C.M. EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB %00100010 ; Press the left and B buttons (%00100010) for 30 EQUB 30 ; VBlanks to move the icon bar pointer to the left ; and onto the Status Mode button EQUB $83 ; Do nothing for 3 * 4 = 12 VBlanks EQUB %00101000 ; Press the up and B buttons (%00101000) for 10 EQUB 10 ; VBlanks to increase our speed EQUB $C3 ; Do nothing (%00000000) while bit 7 of ENERGY is set EQUB %00000000 ; (i.e. do nothing until our energy levels start to EQUW ENERGY ; deplete as the station's Vipers attack us and blast ; away our shields) EQUB %00010000 ; Press the Select button (%00010000) for 3 VBlanks to EQUB 3 ; choose the selected icon (i.e. Status Mode) so we can ; see the commander image flashing with a red background ; until we finally reach the Game Over screen EQUB $88 ; Do nothing for 8 * 4 = 32 VBlanks EQUB $80 ; Quit auto-play and return to the title screen
Name: AutoPlayDemo [Show more] Type: Subroutine Category: Combat demo Summary: Automatically play the demo using the auto-play commands from the autoplayKeys tables Deep dive: Auto-playing the combat demo
Context: See this subroutine on its own page References: This subroutine is called as follows: * NMI calls AutoPlayDemo
.AutoPlayDemo LDA controller1A ; If no buttons are being pressed on controller 1, jump ORA controller1B ; to auto1 to continue with the auto-playing of the demo ORA controller1Left ORA controller1Right ORA controller1Up ORA controller1Down ORA controller1Start ORA controller1Select BPL auto1 LDA #0 ; Otherwise a button has been pressed, so we disable STA autoPlayDemo ; auto-play by setting autoPlayDemo to zero RTS ; Return from the subroutine .auto1 LDX autoPlayRepeat ; If autoPlayRepeat is non-zero then this means a BNE auto4 ; previous auto-play step has set a repeat action and ; we still have some repeats to go, so jump to auto4 ; to decrement the repeat counter and press the buttons ; in autoPlayKey for this VBlank LDY #0 ; Set Y = 0 to use as an index when fetching auto-play ; bytes from the relevant autoPlayKeys table LDA (autoPlayKeys),Y ; Set A to byte #1 of this auto-play command BMI auto5 ; If bit 7 of byte #1 is set, jump to auto5 ; If we get here then bit 7 of byte #1 is clear and A ; contains byte #1 STA autoPlayKey ; Set autoPlayKey to byte #1 so we perform the button ; presses in byte #1 INY ; Set A to byte #2 of this auto-play command LDA (autoPlayKeys),Y SEC ; Set the C flag so the addition below adds an extra 1, ; so autoPlayKeys(1 0) gets incremented by 2 (as we have ; just processed two bytes) TAX ; Set X to byte #2, so this gets set as the number of ; repeats in autoPlayRepeat .auto2 LDA #1 ; Set A = 1 so the following adds 1 + C to the address ; in autoPlayKeys(1 0), so we move the pointer to the ; byte we are processing next on by 1 + C bytes .auto3 ADC autoPlayKeys ; Set autoPlayKeys(1 0) = autoPlayKeys(1 0) + 1 + C STA autoPlayKeys BCC auto4 INC autoPlayKeys+1 .auto4 DEX ; Decrement the repeat counter in autoPlayRepeat, as we STX autoPlayRepeat ; are about to press the buttons LDA autoPlayKey ; Set A to the buttons to be pressed in autoPlayKey, ; which has the following format: ; ; * Bit 0 = right button ; * Bit 1 = left button ; * Bit 2 = down button ; * Bit 3 = up button ; * Bit 4 = Select button ; * Bit 5 = B button ; * Bit 6 = A button ; ; Bit 7 is always clear ASL controller1Right ; Set bit 7 of controller1Right to bit 0 of autoPlayKey LSR A ; to "press" the right button ROR controller1Right ASL controller1Left ; Set bit 7 of controller1Left to bit 0 of autoPlayKey LSR A ; to "press" the left button ROR controller1Left ASL controller1Down ; Set bit 7 of controller1Down to bit 0 of autoPlayKey LSR A ; to "press" the down button ROR controller1Down ASL controller1Up ; Set bit 7 of controller1Up to bit 0 of autoPlayKey LSR A ; to "press" the up button ROR controller1Up ASL controller1Select ; Set bit 7 of controller1Select to bit 0 of autoPlayKey LSR A ; to "press" the Select button ROR controller1Select ASL controller1B ; Set bit 7 of controller1B to bit 0 of autoPlayKey LSR A ; to "press" the B button ROR controller1B ASL controller1A ; Set bit 7 of controller1A to bit 0 of autoPlayKey LSR A ; to "press" the A button ROR controller1A RTS ; We have now pressed the correct buttons for this ; VBlank, so return from the subroutine .auto5 ; If we get here then bit 7 of byte #1 is set and A ; contains byte #1 ASL A ; Set A = A << 1, so A contains byte #1 << 1 BEQ auto14 ; If the result is zero then byte #1 must be $80, so ; jump to auto14 with A = 0 to terminate auto-play BMI auto7 ; If bit 6 of byte #1 is set, jump to auto7 ; If we get here then bit 7 of byte #1 is set, bit 6 of ; byte #1 is clear, and A contains byte #1 << 1 ; ; So byte #1 = $C0, which means we do nothing for ; 4 * byte #1 (ignoring bit 7 of byte #1) ASL A ; Set A = A << 1, so A contains byte #1 << 2 TAX ; Set X to byte #1 << 2, so this gets set as the number ; of repeats in autoPlayRepeat when we jump up to auto2 ; below (so this sets the number of repetitions to ; byte #1 << 2, which is 4 * byte #1 (if we ignore bit 7 ; of byte #1) .auto6 LDA #0 ; Set autoPlayKey = 0 so no buttons are pressed in the STA autoPlayKey ; next VBlank BEQ auto2 ; Jump to auto2 to process the button-pressing in this ; VBlank (this BEQ is effectively a JMP as A is always ; zero) .auto7 ; If we get here then bits 6 and 7 of byte #1 are set ; and A contains byte #1 << 1, so byte #1 is of the form ; $Cx, where x is any value ASL A ; Set A = A << 1, so A contains byte #1 << 2 BEQ auto13 ; If the result is zero then byte #1 must be $C0, so ; jump to auto13 to switch to the auto-play commands in ; the autoPlayKeys2 table, which we will start ; processing in the next NMI ; If we get here then bits 6 and 7 of byte #1 are set ; and A contains byte #1 << 1, so byte #1 is of the form ; $Cx where x is non-zero PHA ; Store byte #1 << 2 on the stack INY ; Set A to byte #2 of this auto-play command LDA (autoPlayKeys),Y STA autoPlayKey ; Set autoPlayKey to byte #2 so we perform the button ; presses in byte #2 INY ; Set A to byte #3 of this auto-play command LDA (autoPlayKeys),Y STA addr ; Set the low byte of addr(1 0) to byte #3 INY ; Set A to byte #4 of this auto-play command LDA (autoPlayKeys),Y STA addr+1 ; Set the high byte of addr(1 0) to byte #3, so we now ; have addr(1 0) = (byte #3 byte #4) ; We now process the auto-play commands for when byte #1 ; is $C1 through $C5 LDY #0 ; Set Y = 0 so we can use indirect addressing below (we ; do not change the value of Y, this is just so we can ; implement the non-existent LDA (addr) instruction by ; using LDA (addr),Y instead) LDX #1 ; Set X = 1 this gets set as the number of repeats in ; autoPlayRepeat when we jump up to auto2 below, so the ; command will do each button press just once before ; re-checking the criteria in the next VBlank PLA ; Set A = byte #1 << 2 ; ; In other words A is the low nibble of byte #1 ; multiplied by 4, so we can check this value to ; determine the command in byte #1, as follows: ; ; * If byte #1 = $C1, A = 1 * 4 = 4 ; ; * If byte #1 = $C2, A = 2 * 4 = 8 ; ; * If byte #1 = $C3, A = 3 * 4 = 12 ; ; * If byte #1 = $C4, A = 4 * 4 = 16 ; ; * If byte #1 = $C5, A = 5 * 4 = 20 CMP #8 ; If A >= 8 then byte #1 is not $C1, so jump to auto9 BCS auto9 ; If we get here then byte #1 is $C1, so we repeat the ; button presses in byte #2 while addr(1 0) <> 0 LDA (addr),Y ; Set A = addr(1 0) BNE auto4 ; If addr(1 0) <> 0, jump to auto4 to do the button ; presses in byte #2 (which we put into autoPlayKey ; above), and because we have not updated the pointer ; in autoPlayKeys(1 0), we will come back to this exact ; same check in the next VBlank, and so on until the ; condition changes and addr(1 0) = 0 ; If addr(1 0) = 0 then fall through into auto8 to ; advance the pointer in autoPlayKeys(1 0) by 4, so in ; the next VBlank, we move on to the next command after ; byte #3 .auto8 LDA #4 ; Set A = 4 and clear the C flag, so in the jump to CLC ; auto3, we advance the pointer in autoPlayKeys(1 0) by ; 4 and return from the subroutine BCC auto3 ; Jump to auto3 to advance the pointer and return from ; the subroutine (this BCC is effectively a JMP as we ; just cleared the C flag) .auto9 ; If we get here then byte #1 is $C2 to $C5, we just ; performed a CMP #8, and A = byte #1 << 2 BNE auto10 ; If A <> 8 then byte #1 is not $C2, so jump to auto10 ; If we get here then byte #1 is $C2, so we repeat the ; button presses in byte #2 while addr(1 0) = 0 LDA (addr),Y ; Set A = addr(1 0) BEQ auto4 ; If addr(1 0) = 0, jump to auto4 to do the button ; presses in byte #2 (which we put into autoPlayKey ; above), and because we have not updated the pointer ; in autoPlayKeys(1 0), we will come back to this exact ; same check in the next VBlank, and so on until the ; condition changes and addr(1 0) <> 0 BNE auto8 ; If addr(1 0) <> 0 then jump to auto8 to advance the ; pointer in autoPlayKeys(1 0) by 4, so in the next ; VBlank, we move on to the next command after byte #3 ; (this BNE is effectively a JMP as we just passed ; through a BEQ) .auto10 ; If we get here then byte #1 is $C3 to $C5, and ; A = byte #1 << 2 CMP #16 ; if A >= 16 then byte #1 is not $C3, so jump to auto11 BCS auto11 ; If we get here then byte #1 is $C3, so we repeat the ; button presses in byte #2 while bit 7 of addr(1 0) is ; set LDA (addr),Y ; Set A = addr(1 0) BMI auto4 ; If bit 7 of addr(1 0) is set, jump to auto4 to do the ; button presses in byte #2 (which we put into ; autoPlayKey above), and because we have not updated ; the pointer in autoPlayKeys(1 0), we will come back to ; this exact same check in the next VBlank, and so on ; until the condition changes and bit 7 of addr(1 0) is ; clear BPL auto8 ; If bit 7 of addr(1 0) is clear then jump to auto8 to ; advance the pointer in autoPlayKeys(1 0) by 4, so in ; the next VBlank, we move on to the next command after ; byte #3 (this BPL is effectively a JMP as we just ; passed through a BMI) .auto11 ; If we get here then byte #1 is $C4 to $C5, we just ; performed a CMP #16, and A = byte #1 << 2 BNE auto12 ; If A <> 16 then byte #1 is not $C4, so jump to auto12 ; If we get here then byte #1 is $C4, so we repeat the ; button presses in byte #2 while bit 7 of addr(1 0) is ; clear LDA (addr),Y ; Set A = addr(1 0) BMI auto8 ; If bit 7 of addr(1 0) is set then jump to auto8 to ; advance the pointer in autoPlayKeys(1 0) by 4, so in ; the next VBlank, we move on to the next command after ; byte #3 (this BPL is effectively a JMP as we just ; passed through a BMI) JMP auto4 ; Otherwise bit 7 of addr(1 0) is clear, so jump to ; auto4 to do the button presses in byte #2 (which we ; put into autoPlayKey above), and because we have not ; updated the pointer in autoPlayKeys(1 0), we will come ; back to this exact same check in the next VBlank, and ; so on until the condition changes and bit 7 of ; addr(1 0) is set .auto12 ; If we get here then byte #1 is $C5, so we terminate ; auto-play with the Start button being held down LDA #%11000000 ; Set bits 6 and 7 of controller1Start to simulate the STA controller1Start ; Start button being held down for two VBlanks LDX #22 ; Set X = 22, so this gets set as the number of repeats ; autoPlayRepeat when we jump to auto2 via auto6 below ; (so this ensures we do nothing for 22 VBlanks after ; pressing the Start button) CLC ; Clear the C flag so the jump to auto2 via auto6 only ; adds one to the pointer in autoPlayKeys(1 0), so we ; move on to the command after byte #1 when we have ; completed the 22 VBlanks of inactivity BCC auto6 ; Jump to auto6 to set autoPlayKey = 0 so no buttons are ; pressed in the following VBlanks, and move on to auto2 ; to process the button-pressing in this VBlank (this ; BCC is effectively a JMP as we just cleared the C ; flag) .auto13 ; If we get here then byte #1 is $C0 and we need to ; switch to the auto-play commands in the autoPlayKeys2 ; table, which we will start processing in the next NMI LDA #HI(autoPlayKeys2) ; Set autoPlayKeys(1 0) = autoPlayKeys2 STA autoPlayKeys+1 ; LDA #LO(autoPlayKeys2) ; So the next time we call AutoPlayDemo, in the next STA autoPlayKeys ; call to the NMI handler at the next VBlank, we will ; start pulling auto-play commands from autoPlayKeys2 ; instead of the language-specific table we've been ; using up to this point RTS ; Return from the subroutine .auto14 ; If we get here then byte #1 is $80 and we need to ; terminate auto-play STA autoPlayDemo ; We jump here with A = 0, so this disables auto-play ; by setting autoPlayDemo to zero RTS ; Return from the subroutine
Name: HideIconBarPointer [Show more] Type: Subroutine Category: Icon bar Summary: Clear the icon bar choice and hide the icon bar pointer
Context: See this subroutine on its own page References: This subroutine is called as follows: * MoveIconBarPointer calls HideIconBarPointer * MoveIconBarPointer calls via hipo2

Other entry points: hipo2 Clear the icon button choice and hide the icon bar pointer
.HideIconBarPointer LDA controller1Start ; If the Start button on controller 1 was being held AND #%11000000 ; down (bit 6 is set) but is no longer being held down CMP #%01000000 ; (bit 7 is clear) then keep going, otherwise jump to BNE hipo1 ; hipo1 LDA #80 ; The Start button has been pressed and released, so STA iconBarChoice ; set iconBarChoice to 80 to record this BNE hipo3 ; Jump to hipo3 to hide the icon bar pointer and return ; from the subroutine .hipo1 LDA iconBarChoice ; If iconBarChoice = 80 then we have already recorded CMP #80 ; that the Start button has been pressed but this has BEQ hipo3 ; not yet been processed (as otherwise it would have ; been zeroed), so jump to hipo3 to hide the icon bar ; pointer and return from the subroutine .hipo2 LDA #0 ; Set iconBarChoice = 0 to clear the icon button choice STA iconBarChoice ; so we don't process it again .hipo3 LDA #240 ; Set A to the y-coordinate that's just below the bottom ; of the screen, so we can hide the icon bar pointer ; sprites by moving them off-screen STA ySprite1 ; Set the y-coordinates for the four icon bar pointer STA ySprite2 ; sprites to 240, to move them off-screen STA ySprite3 STA ySprite4 RTS ; Return from the subroutine
Name: SetIconBarPointer [Show more] Type: Subroutine Category: Icon bar Summary: Set the icon bar pointer to a specific position
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH2 calls SetIconBarPointer * SetDemoAutoPlay calls SetIconBarPointer

Arguments: A The button number on which to position the pointer
.SetIconBarPointer ASL A ; Set xIconBarPointer = A * 4 ASL A ; STA xIconBarPointer ; As xIconBarPointer contains the x-coordinate of the ; icon bar pointer, incrementing by 4 for each button LDX #0 ; Zero all the pointer timer and movement variables so STX pointerMoveCounter ; the pointer is not moving and the MoveIconBarPointer STX xPointerDelta ; routine does not start looking for double-taps of STX pointerPressedB ; the B button STX pointerTimer IF _PAL STX pointerTimerB ; Reset the PAL-specific timer that controls whether a ; tap on the B button is the second tap of a double-tap ENDIF RTS ; Return from the subroutine
Name: MoveIconBarPointer [Show more] Type: Subroutine Category: Icon bar Summary: Move the sprites that make up the icon bar pointer and record any choices Deep dive: Sprite usage in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * NMI calls MoveIconBarPointer
.MoveIconBarPointer ; This routine is called every VBlank and manages the ; movement of the icon bar pointer and choosing icon bar ; buttons ; ; We start by updating a couple of counters that are ; only used in the PAL version, and which are ignored in ; the NTSC version DEC pointerTimer ; Decrement the pointer timer ; ; This timer is used in the PAL version to detect the B ; button being pressed twice in quick succession ; ; The pointer timer is updated in the NTSC version but ; is otherwise ignored IF _PAL BNE mbar1 ; If the pointer timer has not reached zero, jump to ; mbar1 to skip the following instruction LSR pointerTimerB ; Zero pointerTimerB (this works because pointerTimerB ; is only ever 0 or 1) ; ; The pointerTimerB timer is used in the PAL version to ; detect the B button being pressed twice in quick ; succession, so zeroing it indicates that the timer has ; run down before the B button was pressed for the ; second time, so this can't be a double-tap ; ; The NTSC version does away with this variable ; altogether, as well as ignoring pointerTimer .mbar1 ENDIF BPL mbar2 ; If pointerTimer is positive, jump to mbar2 to skip ; the following instruction INC pointerTimer ; Increment pointerTimer so it doesn't decrement past ; zero .mbar2 DEC pointerMoveCounter ; Decrement the pointer move counter, which is used to ; keep track of whether the icon bar pointer is moving ; between two buttons (if the counter is non-zero, then ; the pointer is currently moving between two buttons) BPL mbar3 ; If pointerMoveCounter is positive, jump to mbar3 to ; skip the following instruction INC pointerMoveCounter ; Increment pointerMoveCounter so it doesn't decrement ; past zero .mbar3 ; We now confirm that there is an icon bar for us to ; manage (if not, we leave the subroutine at this point) LDA screenFadedToBlack ; If bit 7 of screenFadedToBlack is set then we have BMI hipo2 ; already faded the screen to black, so jump to hipo2 ; to clear the icon button choice and hide the icon bar ; pointer LDA showIconBarPointer ; If showIconBarPointer = 0 then the icon bar pointer BEQ HideIconBarPointer ; should be hidden, so jump to HideIconBarPointer to do ; just that ; If we get here then the icon bar pointer is visible, ; so we now need to process any movement before drawing ; the pointer in its new location ; ; The pointer coordinates are stored in xIconBarPointer ; and yIconBarPointer, though note that the x-coordinate ; in xIconBarPointer is multiplied by 5 to get the final ; pixel x-coordinate (the yIconBarPointer value, on the ; other hand, contains a pixel coordinate from the off) ; ; The movement of the pointer is stored in xPointerDelta ; as a delta, which is set later in the routine ; according to the buttons being pressed on the ; controller ; ; The xPointerDelta variable is set to zero by default, ; and is only non-zero if the previous call to this ; routine detected the correct movement buttons LDA xPointerDelta ; Set xIconBarPointer = xIconBarPointer + xPointerDelta CLC ; ADC xIconBarPointer ; So this updates the x-coordinate of the icon bar STA xIconBarPointer ; to move it in the direction of xPointerDelta (which ; was set to -1, 0 or +1 depending on which directional ; keys were being pressed the last time we were here) AND #3 ; If xIconBarPointer mod 4 is non-zero, jump to mbar9 to BNE mbar9 ; skip updating the movement delta in xPointerDelta, so ; we only scan for movement keys every four movements of ; the pointer (this ensures that when a movement starts, ; it runs for four VBlanks without being interrupted, so ; the pointer keeps moving towards the next button, one ; step for each VBlank) ; ; In other words, when xIconBarPointer mod 4 is 0, the ; pointer is on a button, while other values mean it is ; between buttons ; If we get here then the movement has been applied for ; four VBlanks, so the pointer has now moved onto the ; next button LDA #0 ; Set xPointerDelta = 0 so the pointer is not moving by STA xPointerDelta ; default, though we now change that if the movement ; buttons are being pressed LDA pointerMoveCounter ; If pointerMoveCounter is non-zero then we are already BNE mbar9 ; moving the pointer between two buttons, so jump to ; mbar9 to leave xPointerDelta at zero and ignore any ; button presses, as we need to finish the jump from one ; button to another before we can move it again ; ; This ensures that once we start a movement between ; icon bar buttons, we wait until pointerMoveCounter ; VBlanks have passed before listening for the next move ; ; pointerMoveCounter is set to 12 at the start of each ; move, so we spend the first four VBlanks moving the ; pointer, then xPointerDelta is zeroed above, and then ; we wait for another eight VBlanks before listening for ; button presses again ; ; This gives the icon bar pointer a stepped movement ; that jumps from button to button if the left or right ; buttons are held down LDA controller1B ; If the B button is not being pressed on controller 1 ORA numberOfPilots ; and the game is configured for one pilot, jump to BPL mbar9 ; mbar9 to skip updating the movement delta in ; xPointerDelta, as in one-pilot mode we can only move ; the icon bar pointer when the B button is held down ; ; If the game is configured for two pilots, we always ; pass through this branch as numberOfPilots = 1, so ; we don't need the B button to be held down when there ; are two pilots ; We now process the left button, which moves the icon ; bar pointer to the left LDX controller1Left ; If the left button on controller 1 is being pressed, BMI mbar4 ; jump to mbar4 to set xPointerDelta to -1 LDA #0 ; Otherwise reset controller1Left to 0 to clear out the STA controller1Left ; left button history in the controller variable, to ; make the logic below slightly simpler (see mbar13) JMP mbar6 ; Jump to mbar6 to check for the right button .mbar4 ; If we get here then the left button is being pressed ; and X contains the value of controller1Left LDA #$FF ; Set A = -1 to set as the value of xPointerDelta CPX #%10000000 ; If the left button has just been pressed but wasn't BNE mbar5 ; being pressed before, keep going, otherwise jump to ; mbar5 to skip the following ; The following is therefore only run when we first ; press the left button LDX #12 ; The left button was just pressed but wasn't being STX pointerMoveCounter ; pressed before, so set pointerMoveCounter to 12 so it ; can count down to zero, during which time we don't ; check for any more directional button presses (as the ; pointer will be moving between icon bar buttons) .mbar5 STA xPointerDelta ; Set xPointerDelta = -1, so the pointer moves to the ; left .mbar6 ; We now process the right button, which moves the icon ; bar pointer to the right LDX controller1Right ; If the right button on controller 1 is being pressed, BMI mbar7 ; jump to mbar7 to set xPointerDelta to 1 LDA #0 ; Reset controller1Right to 0 to clear out the right STA controller1Right ; button history in the controller variable, to ; make the logic below slightly simpler (see mbar13) JMP mbar9 ; Jump to mbar9 to move on to clipping the pointer's ; x-coordinate .mbar7 LDA #1 ; Set A = 1 to set as the value of xPointerDelta CPX #%10000000 ; If the right button has just been pressed but wasn't BNE mbar8 ; being pressed before, keep going, otherwise jump to ; mbar8 to skip the following ; The following is therefore only run when we first ; press the right button LDX #12 ; The right button was just pressed but wasn't being STX pointerMoveCounter ; pressed before, so set pointerMoveCounter to 12 so it ; can count down to zero, during which time we don't ; check for any more directional button presses (as the ; pointer will be moving between icon bar buttons) .mbar8 STA xPointerDelta ; Set xPointerDelta = 1, so the pointer moves to the ; left .mbar9 ; We now clip the x-coordinate of the pointer to ensure ; it is in the range 0 to 44 (which equates to a pixel ; range of 0 to 44 * 5 = 220) LDA xIconBarPointer ; If xIconBarPointer < 128, jump to mbar10 to skip the BPL mbar10 ; following LDA #0 ; If we get here then xIconBarPointer >= 128, so set STA xPointerDelta ; xPointerDelta = 0 to stop the pointer from moving BEQ mbar11 ; Jump to mbar11 with A = 0 to set xIconBarPointer = 0, ; so the value of xIconBarPointer wraps around to zero ; if it goes above 127 (this BEQ is effectively a JMP ; as A is always zero) .mbar10 CMP #45 ; If xIconBarPointer < 45, jump to mbar11 to move on to BCC mbar11 ; the next set of checks LDA #0 ; If we get here then 45 <= xIconBarPointer < 127, so STA xPointerDelta ; set xPointerDelta = 0 to stop the pointer from moving LDA #44 ; Set A = 44 to store in xIconBarPointer, so the value ; of xIconBarPointer never gets above 44 .mbar11 STA xIconBarPointer ; Set xIconBarPointer to the clipped value in A, so ; xIconBarPointer is in the range 0 to 44 ; We now draw the icon bar pointer in either the up or ; down position ; ; We draw it in the up position if any of the following ; are true: ; ; * xIconBarPointer mod 4 is non-zero (in which case ; we know that the pointer is moving between ; buttons) ; ; * xPointerDelta is non-zero (so the pointer only ; ever moves when it is in the up position) ; ; * The B button is being pressed (which is the button ; we press to lift the pointer up) ; ; * The Select button is being pressed (so the pointer ; jumps up when we choose an icon from the icon bar ; with Select) ; ; Otherwise we draw it in the down position LDA xIconBarPointer ; If xIconBarPointer mod 4 is non-zero or xPointerDelta AND #3 ; is non-zero, then as noted above, this means that the ORA xPointerDelta ; pointer is between buttons, so jump to mbar12 to draw BNE mbar12 ; the icon bar pointer in the up position LDA controller1B ; If the B button is being pressed, jump to mbar12 to BMI mbar12 ; draw the icon bar pointer in the up position (this LDA controller1B ; comparison is repeated, but that doesn't seem to have BMI mbar12 ; any effect) LDA controller1Select ; If the Select button is being pressed, jump to mbar12 BNE mbar12 ; to draw the icon bar pointer in the up position ; If we get here then the B button is not being pressed, ; so we draw the icon bar pointer in the down position, ; so it looks as if it goes around the bottom of the ; button ; ; The pointer is made up of the following sprites, which ; are ordered in a clockwise fashion: ; ; * Sprite 1 in the top-left ; * Sprite 2 in the top-right ; * Sprite 3 in the bottom-right ; * Sprite 4 in the bottom-left ; ; The value of yIconBarPointer contains the y-coordinate ; of the icon bar, which is 148 when there is a ; dashboard or this is the Game Over screen, or 204 ; otherwise ; ; The value of xIconBarPointer is in the range 0 to 44, ; which represents the icon bar with buttons on each ; multiple of 4 LDA #251 ; Set the pattern number for the sprites 1 and 2 to STA pattSprite1 ; pattern 251, so the top part of the pointer appears to STA pattSprite2 ; go behind the button LDA yIconBarPointer ; Set the y-coordinate of the top of the pointer in CLC ; sprites 1 and 2 to yIconBarPointer + 11, so the ADC #11+YPAL ; pointer is drawn in the down position (three pixels STA ySprite1 ; lower down the screen than the up position) STA ySprite2 LDA xIconBarPointer ; Set A = 6 + xIconBarPointer * 4 + xIconBarPointer ASL A ; = 6 + 5 * xIconBarPointer ASL A ; ADC xIconBarPointer ; As noted above, xIconBarPointer is in the range 0 to ADC #6 ; 44, so the pixel x-coordinate of the pointer is in ; the range 6 to 226 ; We now use A as the x-coordinate of the bottom-left ; corner of the four-sprite pointer by setting the ; sprite's coordinates as follows (with the bottom ; sprites being spread out slightly more than the top ; sprites) STA xSprite4 ; Set the x-coordinate of sprite 4 in the bottom-left ; of the pointer to A ADC #1 ; Set the x-coordinate of sprite 1 in the top-left of STA xSprite1 ; the pointer to A + 1 ADC #13 ; Set the x-coordinate of sprite 2 in the top-right of STA xSprite2 ; the pointer to A + 14 ADC #1 ; Set the x-coordinate of sprite 3 in the bottom-right STA xSprite3 ; of the pointer to A + 15 LDA yIconBarPointer ; Set the y-coordinate of the bottom of the pointer in CLC ; sprites 3 and 4 to yIconBarPointer + 19, so the ADC #19+YPAL ; pointer is drawn in the down position (three pixels STA ySprite4 ; lower down the screen than the up position) STA ySprite3 LDA xIconBarPointer ; If xIconBarPointer is non-zero then jump to mbar13 BNE mbar13 ; (though this has no effect as that's what we're about ; to do anyway) JMP mbar13 ; Jump to mbar13 to continue checking for button presses .mbar12 ; If we get here then the B button is being pressed, so ; we draw the icon bar pointer in the up position, so it ; looks as if can be moved left or right without being ; blocked by the buttons ; ; The pointer is made up of the following sprites, which ; are ordered in a clockwise fashion: ; ; * Sprite 1 in the top-left ; * Sprite 2 in the top-right ; * Sprite 3 in the bottom-right ; * Sprite 4 in the bottom-left ; ; The value of yIconBarPointer contains the y-coordinate ; of the icon bar, which is 148 when there is a ; dashboard or this is the Game Over screen, or 204 ; otherwise ; ; The value of xIconBarPointer is in the range 0 to 44, ; which represents the icon bar with buttons on each ; multiple of 4 LDA #252 ; Set the pattern number for the sprites 1 and 2 to STA pattSprite1 ; pattern 252, so the top part of the pointer appears to STA pattSprite2 ; pop up from behind the top of the button LDA yIconBarPointer ; Set the y-coordinate of the top of the pointer in CLC ; sprites 1 and 2 to yIconBarPointer + 8, so the ADC #8+YPAL ; pointer is drawn in the up position (three pixels STA ySprite1 ; higher up the screen than the down position) STA ySprite2 LDA xIconBarPointer ; Set A = 6 + xIconBarPointer * 4 + xIconBarPointer ASL A ; = 6 + 5 * xIconBarPointer ASL A ; ADC xIconBarPointer ; As noted above, xIconBarPointer is in the range 0 to ADC #6 ; 44, so the pixel x-coordinate of the pointer is in ; the range 6 to 226 ; We now use A as the x-coordinate of the bottom-left ; corner of the four-sprite pointer by setting the ; sprite's coordinates as follows (with the bottom ; sprites being spread out slightly more than the top ; sprites) STA xSprite4 ; Set the x-coordinate of sprite 4 in the bottom-left ; of the pointer to A ADC #1 ; Set the x-coordinate of sprite 1 in the top-left of STA xSprite1 ; the pointer to A + 1 ADC #13 ; Set the x-coordinate of sprite 2 in the top-right of STA xSprite2 ; the pointer to A + 14 ADC #1 ; Set the x-coordinate of sprite 3 in the bottom-right STA xSprite3 ; of the pointer to A + 15 LDA yIconBarPointer ; Set the y-coordinate of the bottom of the pointer in CLC ; sprites 3 and 4 to yIconBarPointer + 16, so the ADC #16+YPAL ; pointer is drawn in the up position (three pixels STA ySprite4 ; higher up the screen than the down position) STA ySprite3 .mbar13 ; We now check the controller buttons to see if an icon ; bar button has been chosen ; ; The logic for the PAL version is rather more ; convoluted than the NTSC version LDA controller1Left ; If none of the directional buttons are being pressed, ORA controller1Right ; jump to mbar14 to skip the following ORA controller1Up ORA controller1Down BPL mbar14 LDA #0 ; At least one of the directional buttons is being STA pointerPressedB ; pressed, so set pointerPressedB = 0 so we don't look ; for a double-tap of the B button .mbar14 LDA controller1Select ; If the Select button has just been pressed but wasn't AND #%11110000 ; being pressed before, jump to mbar17 to choose the CMP #%10000000 ; button under the pointer BEQ mbar17 LDA controller1B ; If the B button has just been pressed but wasn't being AND #%11000000 ; pressed before, keep going, otherwise jump to mbar15 CMP #%10000000 ; to skip the following BNE mbar15 LDA #30 ; The B button has just been pressed but wasn't being STA pointerPressedB ; pressed before, so set pointerPressedB to a non-zero ; value so we start looking for a double-tap of the B ; button ; ; This value of A also ensures that we jump to mbar18 ; in the next comparison, as %01000000 does not match ; 30, so this essentially sets pointerPressedB to a ; non-zero value and then moves on to the next VBlank, ; leaving the non-zero value to be picked up in a future ; VBlank below .mbar15 CMP #%01000000 ; If the B button was being pressed but has just been BNE mbar18 ; released, keep going, otherwise jump to mbar18 to move ; onto the next set of checks IF _NTSC LDA pointerPressedB ; If pointerPressedB = 0 then jump to mbar18 to move BEQ mbar18 ; onto the next set of checks ; If we get here then pointerPressedB is non-zero, so we ; know the B button was pressed and released in a ; previous VBlank, and we know that in this VBlank, the ; B button was being pressed but has just been released, ; so that's a double-tap on the B button ; ; This is one of the ways of choosing an icon bar icon, ; so fall through into mbar17 to choose the button under ; the pointer ELIF _PAL LDA pointerPressedB ; If pointerPressedB is non-zero then the B button was BNE mbar16 ; tapped and released in a previous VBlank, so jump to ; mbar16 to potentially process a double-tap on the B ; button STA pointerTimerB ; Otherwise zero pointerTimerB, so pointerTimerB is zero ; if we are not looking for a double-tap BEQ mbar18 ; Jump to mbar18 to move on to the next set of checks ; (this BEQ is effectively a JMP as A is always zero) .mbar16 ; If we get here then the B button was tapped and ; released in a previous VBlank LDA #40 ; Set pointerTimer = 40 so it counts down over the next STA pointerTimer ; 40 VBlanks, zeroing pointerTimerB if it runs out ; before the second tap on the B button is detected LDA pointerTimerB ; If pointerTimerB = 1 then we are already looking for BNE mbar17 ; the second tap of a double-tap on the B button, which ; we just found, so jump to mbar17 to choose the button ; under the pointer INC pointerTimerB ; Otherwise increment pointerTimerB to 1 and skip the BNE mbar18 ; following instruction, so we will be on the lookout ; for the second tap of the B button in future VBlanks ENDIF .mbar17 IF _PAL LSR pointerTimerB ; Set pointerTimerB = 0 as we have now chosen an icon ; bar button, so we don't need to check for a double-tap ; on the B button any more ENDIF ; If we get here then we have chosen a button on the ; icon bar, so we update iconBarChoice accordingly LDA xIconBarPointer ; Set Y to the button number that the icon bar pointer LSR A ; is over LSR A TAY LDA (barButtons),Y ; Set iconBarChoice to the Y-th entry from the button STA iconBarChoice ; table for this icon bar to indicate that this icon bar ; button has been selected .mbar18 ; Finally, we check to see if the Start button has been ; tapped, and if so, we record that as an iconBarChoice ; of 80 LDA controller1Start ; If the Start button on controller 1 was being held AND #%11000000 ; down (bit 6 is set) but is no longer being held down CMP #%01000000 ; (bit 7 is clear) then keep going, otherwise jump to BNE mbar19 ; mbar19 LDA #80 ; Set iconBarChoice to indicate that the Start button STA iconBarChoice ; has been pressed .mbar19 RTS ; Return from the subroutine
Name: SetControllerPast [Show more] Type: Subroutine Category: Controllers Summary: Set the controller history variables to the values from four VBlanks ago Deep dive: Bolting NES controllers onto the key logger
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdateJoystick calls SetControllerPast
.SetControllerPast LDA controller1B ; If the B button is being held down, jump to past1 to BNE past1 ; zero the controller history variables, as we don't ; need the controller history for the icon bar movement ; (which is done by holding down the B button while ; using the left and right buttons) LDA controller1Left ; Set the high nibble of the left button history ASL A ; variable to bits 0 to 3 of controller1Left, so it ASL A ; contains the controller values from four VBlanks ago ASL A ASL A STA controller1Left03 LDA controller1Right ; Set the high nibble of the right button history ASL A ; variable to bits 0 to 3 of controller1Right, so it ASL A ; contains the controller values from four VBlanks ago ASL A ASL A STA controller1Right03 RTS ; Return from the subroutine .past1 LDA #0 ; Zero the controller history variables as we don't need STA controller1Left03 ; them for moving the icon bar pointer STA controller1Right03 RTS ; Return from the subroutine
Name: UpdateJoystick [Show more] Type: Subroutine Category: Controllers Summary: Update the values of JSTX and JSTY with the values from the controller 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 UpdateJoystick
.UpdateJoystick LDA QQ11a ; If the old view in QQ11a is not the space view, then BNE SetControllerPast ; jump to SetControllerPast to set the controller ; history variables to the values from four VBlanks ago ; and return from the subroutien using a tail call LDX JSTX ; Set X to the current roll rate in JSTX LDA #8 ; Set joystickDelta = 8, to use as the amount by which STA joystickDelta ; we change the roll rate by for each button press LDY numberOfPilots ; Set Y to numberOfPilots, which will be 0 if only one ; pilot is configured, or 1 if two pilots are configured ; ; As the rest of this routine updates the joystick based ; on the values in controller1Right + Y etc., this means ; that when the game is configured for two pilots, the ; routine updates the joystick variables based on the ; buttons being pressed on controller 2 ; ; In other words, when two pilots are configured, ; controller 2 steers the ship while controller 1 looks ; after the weaponry BNE joys1 ; If numberOfPilots = 1 then the game is configured for ; two pilots, so skip the following so that holding down ; the B button on controller 1 doesn't stop controller 2 ; from updating the flight controls LDA controller1B ; If the B button is being pressed on controller 1 then BMI joys10 ; the arrow should be used to control the icon bar and ; ship speed, rather than the ship's steering, so jump ; to joys10 to return from the subroutine .joys1 LDA controller1Right,Y ; If the right button is not being pressed, jump to BPL joys2 ; joys2 to skip the following instruction JSR DecreaseJoystick ; The right button is being held down, so decrease the ; current roll rate in X by joystickDelta .joys2 LDA controller1Left,Y ; If the left button is not being pressed, jump to joys3 BPL joys3 ; to skip the following instruction JSR IncreaseJoystick ; The left button is being held down, so increase the ; current roll rate in X by joystickDelta .joys3 STX JSTX ; Store the updated roll rate in JSTX TYA ; If Y is non-zero then the game is configured for two BNE joys4 ; pilots, so jump to joys4... though as this is the very ; next line, this has no effect .joys4 LDA #4 ; Set joystickDelta = 4, to use as the amount by which STA joystickDelta ; we change the pitch rate by for each button press LDX JSTY ; Set X to the current pitch rate in JSTY LDA JSTGY ; If JSTGY is $FF then the game is configured to reverse BMI joys8 ; the controller y-axis, so jump to joys8 to change the ; pitch value in the opposite direction LDA controller1Down,Y ; If the down button is not being pressed, jump to joys5 BPL joys5 ; to skip the following instruction JSR DecreaseJoystick ; The down button is being held down, so decrease the ; current pitch rate in X by joystickDelta .joys5 LDA controller1Up,Y ; If the up button is not being pressed, jump to joys7 BPL joys7 ; to skip the following instruction .joys6 JSR IncreaseJoystick ; The up button is being held down, so increase the ; current pitch rate in X by joystickDelta .joys7 STX JSTY ; Store the updated pitch rate in JSTY RTS ; Return from the subroutine .joys8 LDA controller1Up,Y ; If the up button is not being pressed, jump to joys9 BPL joys9 ; to skip the following instruction JSR DecreaseJoystick ; The up button is being held down, so decrease the ; current pitch rate in X by joystickDelta (as the game ; is configured to reverse the joystick Y channel) .joys9 LDA controller1Down,Y ; If the down button is being pressed, jump to joys6 to BMI joys6 ; increase the current pitch rate in X by joystickDelta ; (as the game is configured to reverse the joystick Y ; channel) STX JSTY ; Store the updated pitch rate in JSTY RTS ; Return from the subroutine .joys10 RTS ; Return from the subroutine
Name: IncreaseJoystick [Show more] Type: Subroutine Category: Controllers Summary: Increase a joystick value by a specific amount, jumping straight to the indicator centre if we increase from the left-hand side Deep dive: Bolting NES controllers onto the key logger
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdateJoystick calls IncreaseJoystick

Arguments: X The value (pitch or roll rate) to decrease joystickDelta The amount to decrease the value in X by
.IncreaseJoystick TXA ; Set X = X + joystickDelta CLC ADC joystickDelta TAX BCC incj1 ; If the addition didn't overflow, jump to incj1 to skip ; the following instruction LDX #255 ; Set X = 255 so X doesn't get larger than 255 (so we ; can't go past the right end of the indicator) .incj1 BPL decj2 ; If X < 127 then the increased value is still in the ; left-hand side of the indicator, so jump to decj2 to ; return a value of 128, for the centre of the indicator RTS ; Return from the subroutine
Name: DecreaseJoystick [Show more] Type: Subroutine Category: Controllers Summary: Decrease a joystick value by a specific amount, jumping straight to the indicator centre if we decrease from the right-hand side Deep dive: Bolting NES controllers onto the key logger
Context: See this subroutine on its own page References: This subroutine is called as follows: * UpdateJoystick calls DecreaseJoystick * IncreaseJoystick calls via decj2

Arguments: X The value (pitch or roll rate) to decrease joystickDelta The amount to decrease the value in X by
Other entry points: decj2 Return a value of X = 128, for the centre of the indicator
.DecreaseJoystick TXA ; Set X = X - joystickDelta SEC SBC joystickDelta TAX BCS decj1 ; If the subtraction didn't underflow, jump to decj1 to ; skip the following instruction LDX #1 ; Set X = 1 so X doesn't get smaller than 1 (so we can't ; go past the left end of the indicator) .decj1 BPL decj3 ; If X < 127 then the decreased value is in the ; left-hand side of the indicator, so jump to decj3 to ; return from the subroutine ; If we get here then the decreased value is still in ; the right-hand side of the indicator, so we return a ; value of 128, for the centre of the indicator .decj2 LDX #128 ; Set X = 128 to jump to indicator to the centre of the ; indicator, so increasing or decreasing a value towards ; the centre of the indicator immediately jumps to the ; middle point of the indicator .decj3 RTS ; Return from the subroutine
Name: iconBarButtons [Show more] Type: Variable Category: Icon bar Summary: A list of button numbers for each icon bar type
Context: See this variable on its own page References: This variable is used as follows: * SetIconBarButtons uses iconBarButtons
.iconBarButtons ; Icon bar 0 (Docked) EQUB 1 ; Launch EQUB 2 ; Market Price EQUB 3 ; Status Mode EQUB 4 ; Charts EQUB 5 ; Equip Ship EQUB 6 ; Save and Load EQUB 7 ; Change Commander Name (only on save screen) EQUB 35 ; Data on System EQUB 8 ; Inventory EQUB 0 ; (blank) EQUB 0 ; (blank) EQUB 12 ; Fast-forward EQUD 0 ; Icon bar 1 (Flight) EQUB 17 ; Docking Computer EQUB 2 ; Market Price EQUB 3 ; Status Mode EQUB 4 ; Charts EQUB 21 ; Front Space View (and rear, left, right) EQUB 22 ; Hyperspace (only when system is selected) EQUB 23 ; E.C.M. (if fitted) EQUB 24 ; Target Missile EQUB 25 ; Fire Targeted Missile EQUB 26 ; Energy Bomb (if fitted) EQUB 27 ; Escape Pod (if fitted) EQUB 12 ; Fast-forward EQUD 0 ; Icon bar 2 (Charts) EQUB 1 ; Launch EQUB 2 ; Market Price EQUB 36 ; Switch Chart Range (long, short) EQUB 35 ; Data on System EQUB 21 ; Front Space View (only in flight) EQUB 38 ; Return Pointer to Current System EQUB 39 ; Search for System EQUB 22 ; Hyperspace (only when system is selected) EQUB 41 ; Galactic Hyperspace (if fitted) EQUB 23 ; E.C.M. (if fitted) EQUB 27 ; Escape Pod (if fitted) EQUB 12 ; Fast-forward EQUD 0 ; Icon bar 3 (Pause) EQUB 49 ; Direction of y-axis EQUB 50 ; Damping toggle EQUB 51 ; Music toggle EQUB 52 ; Sound toggle EQUB 53 ; Number of Pilots EQUB 0 ; (blank) EQUB 0 ; (blank) EQUB 0 ; (blank) EQUB 0 ; (blank) EQUB 0 ; (blank) EQUB 0 ; (blank) EQUB 60 ; Restart EQUD 0
Name: HideStardust [Show more] Type: Subroutine Category: Stardust Summary: Hide the stardust sprites
Context: See this subroutine on its own page References: This subroutine is called as follows: * LL164 calls HideStardust
.HideStardust SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX NOSTM ; Set X = NOSTM so we hide NOSTM+1 sprites LDY #152 ; Set Y so we start hiding from sprite 152 / 4 = 38 ; Fall through into HideMoreSprites to hide NOSTM+1 ; sprites from sprite 38 onwards (i.e. 38 to 58 in ; normal space when NOSTM is 20, or 38 to 41 in ; witchspace when NOSTM is 3)
Name: HideMoreSprites [Show more] Type: Subroutine Category: Drawing sprites Summary: Hide X + 1 sprites from sprite Y / 4 onwards
Context: See this subroutine on its own page References: This subroutine is called as follows: * HideMostSprites calls HideMoreSprites

This routine is similar to HideSprites, except it hides X + 1 sprites rather than X sprites.
Arguments: X The number of sprites to hide (we hide X + 1) Y The number of the first sprite to hide * 4
.HideMoreSprites 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 .hisp1 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 BPL hisp1 ; Loop back until we have hidden X + 1 sprites RTS ; Return from the subroutine
Name: SetScreenForUpdate [Show more] Type: Subroutine Category: Drawing sprites Summary: Get the screen ready for updating by hiding all sprites, after fading the screen to black if we are changing view
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuyAndSellCargo calls SetScreenForUpdate * DEATH calls SetScreenForUpdate * EQSHP calls SetScreenForUpdate * SVE calls SetScreenForUpdate * TT23 calls SetScreenForUpdate * TT66 calls SetScreenForUpdate * UpdateViewWithFade calls SetScreenForUpdate
.SetScreenForUpdate LDA QQ11a ; If QQ11 = QQ11a, then we are not currently changing CMP QQ11 ; view, so jump to HideMostSprites to hide all sprites BEQ HideMostSprites ; except for sprite 0 and the icon bar pointer ; Otherwise fall through into FadeAndHideSprites to fade ; the screen to black and hide all the sprites
Name: FadeAndHideSprites [Show more] Type: Subroutine Category: Drawing sprites Summary: Fade the screen to black and hide all sprites
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIEF calls FadeAndHideSprites * STATUS calls FadeAndHideSprites * TT22 calls FadeAndHideSprites * TT25 calls FadeAndHideSprites
.FadeAndHideSprites JSR FadeToBlack_b3 ; Fade the screen to black over the next four VBlanks
Name: HideMostSprites [Show more] Type: Subroutine Category: Drawing sprites Summary: Hide all sprites except for sprite 0 and the icon bar pointer
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH calls HideMostSprites * DrawTitleScreen calls HideMostSprites * LAUN calls HideMostSprites * SetScreenForUpdate calls HideMostSprites
.HideMostSprites SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDX #58 ; Set X = 58 so we hide 59 sprites LDY #20 ; Set Y so we start hiding from sprite 20 / 4 = 5 BNE HideMoreSprites ; Jump to HideMoreSprites to hide 59 sprites from ; sprite 5 onwards (i.e. sprites 5 to 63, which only ; leaves sprite 0 and the icon bar pointer sprites 1 to ; 4) ; ; We return from the subroutine using a tail call (this ; BNE is effectively a JMP as Y is never zero)
Name: DELAY [Show more] Type: Subroutine Category: Utility routines Summary: Wait until a specified number of NMI interrupts have passed (i.e. a specified number of VBlanks)
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIS calls DELAY * BRIS_b0 calls DELAY * ChangeLetter calls DELAY * DOENTRY calls DELAY * eq calls DELAY * EQSHP calls DELAY * LAUN calls DELAY * Main game loop (Part 5 of 6) calls DELAY * PauseGame calls DELAY * SetupDemoUniverse calls DELAY * YESNO calls DELAY


Arguments: Y The number of NMI interrupts to wait for
.DELAY JSR WaitForNMI ; Wait until the next NMI interrupt has passed (i.e. the ; next VBlank) DEY ; Decrement the counter in Y BNE DELAY ; If Y isn't yet at zero, jump back to DELAY to wait ; for another NMI interrupt RTS ; Return from the subroutine
Name: BEEP [Show more] Type: Subroutine Category: Sound Summary: Make a short, high beep
Context: See this subroutine on its own page References: This subroutine is called as follows: * BEEP_b7 calls BEEP
.BEEP LDY #3 ; Call the NOISE routine with Y = 3 to make a short, BNE NOISE ; high beep, returning from the subroutine using a tail ; call (this BNE is effectively a JMP as Y will never be ; zero)
Name: EXNO3 [Show more] Type: Subroutine Category: Sound Summary: Make an explosion sound
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH calls EXNO3 * Main flight loop (Part 10 of 16) calls EXNO3 * OOPS calls EXNO3 * TACTICS (Part 1 of 7) calls EXNO3

Make the sound of death in the cold, hard vacuum of space. Apparently, in Elite space, everyone can hear you scream. This routine also makes the sound of a destroyed cargo canister if we don't get scooping right, the sound of us colliding with another ship, and the sound of us being hit with depleted shields. It is not a good sound to hear.
.EXNO3 LDY #13 ; Call the NOISE routine with Y = 13 to make the sound BNE NOISE ; of an explosion, returning from the subroutine using ; a tail call (this BNE is effectively a JMP as Y will ; never be zero)
Name: FlushSoundChannels [Show more] Type: Subroutine Category: Sound Summary: Flush the SQ1, SQ2 and NOISE sound channels
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeHyperSound calls FlushSoundChannels
.FlushSoundChannels LDX #0 ; Flush the SQ1 sound channel JSR FlushSoundChannel ; Fall through into FlushSQ2AndNOISE to flush the SQ2 ; and NOISE channels
Name: FlushSQ2AndNOISE [Show more] Type: Subroutine Category: Sound Summary: Flush the SQ2 and NOISE sound channels
Context: See this subroutine on its own page References: This subroutine is called as follows: * FlushSpecificSound calls FlushSQ2AndNOISE
.FlushSQ2AndNOISE LDX #1 ; Flush the SQ2 sound channel JSR FlushSoundChannel LDX #2 ; Flush the NOISE sound channel, returning from the BNE FlushSoundChannel ; subroutine using a tail call
Name: FlushSpecificSound [Show more] Type: Subroutine Category: Sound Summary: Flush the channels used by a specific sound
Context: See this subroutine on its own page References: This subroutine is called as follows: * ECMOF calls FlushSpecificSound

The sound channels are flushed according to the specific sound's value in the soundChannel table: * If soundChannel = 0, flush the SQ1 sound channel * If soundChannel = 1, flush the SQ2 sound channel * If soundChannel = 2, flush the NOISE sound channel * If soundChannel = 3, flush the SQ1 and NOISE sound channels * If soundChannel = 4, flush the SQ2 and NOISE sound channels
Arguments: Y The number of the sound to flush
.FlushSpecificSound LDX soundChannel,Y ; Set X to the sound channel for sound Y CPX #3 ; If X < 3 then jump to FlushSoundChannel to flush the BCC FlushSoundChannel ; SQ1, SQ2 or NOISE sound channel, as specified in X, ; returning from the subroutine using a tail call BNE FlushSQ2AndNOISE ; If X <> 3, i.e. X = 4, then jump to FlushSQ2AndNOISE ; to flush sound channels 1 and 2, returning from the ; subroutine using a tail call ; If we get here then we know X = 3, so now we flush the ; SQ1 and NOISE sound channels LDX #0 ; Flush the SQ1 sound channel JSR FlushSoundChannel LDX #2 ; Set X = 2 and fall through into FlushSoundChannel to ; flush the NOISE sound channel
Name: FlushSoundChannel [Show more] Type: Subroutine Category: Sound Summary: Flush a specific sound channel Deep dive: Sound effects in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * FlushSoundChannels calls FlushSoundChannel * FlushSpecificSound calls FlushSoundChannel * FlushSQ2AndNOISE calls FlushSoundChannel

Arguments: X The sound channel to flush: * 0 = flush the SQ1 sound channel * 1 = flush the SQ2 sound channel * 2 = flush the NOISE sound channel
.FlushSoundChannel 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 #0 ; Set the priority for channel X to zero to stop the STA channelPriority,X ; channel from making any more sounds LDA #26 ; Set A = 26 to pass to StartEffect below (sound effect ; 26 is the sound of silence, so this flushes the sound ; channel) BNE StartEffect_b7 ; Jump to StartEffect to start making sound effect 26 ; on channel X (this BNE is effectively a JMP as A is ; never zero)
Name: BOOP [Show more] Type: Subroutine Category: Sound Summary: Make a long, low beep
Context: See this subroutine on its own page References: This subroutine is called as follows: * eq calls BOOP * EQSHP calls BOOP * HME2 calls BOOP * TT102 calls BOOP
.BOOP LDY #4 ; Call the NOISE routine with Y = 4 to make a long, low BNE NOISE ; beep, returning from the subroutine using a tail call ; (this BNE is effectively a JMP as Y will never be ; zero)
Name: MakeScoopSound [Show more] Type: Subroutine Category: Sound Summary: Make the sound of the fuel scoops working
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 8 of 16) calls MakeScoopSound * Main flight loop (Part 15 of 16) calls MakeScoopSound
.MakeScoopSound LDY #1 ; Call the NOISE routine with Y = 1 to make the sound of BNE NOISE ; the fuel scoops working, returning from the subroutine ; using a tail call (this BNE is effectively a JMP as Y ; will never be zero)
Name: MakeHyperSound [Show more] Type: Subroutine Category: Sound Summary: Make the hyperspace sound
Context: See this subroutine on its own page References: This subroutine is called as follows: * LL164 calls MakeHyperSound * TT18 calls MakeHyperSound
.MakeHyperSound JSR FlushSoundChannels ; Flush the SQ1, SQ2 and NOISE sound channels LDY #21 ; Set Y = 21 and fall through into the NOISE routine to ; make the hyperspace sound
Name: NOISE [Show more] Type: Subroutine Category: Sound Summary: Make the sound effect whose number is in Y Deep dive: Sound effects in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * BEEP calls NOISE * BOOP calls NOISE * BuyAndSellCargo calls NOISE * ECBLB2 calls NOISE * ESCAPE calls NOISE * EXNO calls NOISE * EXNO2 calls NOISE * EXNO3 calls NOISE * FRMIS calls NOISE * Ghy calls NOISE * LAUN calls NOISE * Main flight loop (Part 3 of 16) calls NOISE * Main flight loop (Part 15 of 16) calls NOISE * Main game loop (Part 5 of 6) calls NOISE * MakeScoopSound calls NOISE * MJP calls NOISE * SFRMIS calls NOISE * TACTICS (Part 6 of 7) calls NOISE

Arguments: Y The number of the sound effect to be made
.NOISE LDA DNOIZ ; If DNOIZ is zero then sound is disabled, so jump to BPL RTS8 ; RTS8 to return from the subroutine without making a ; sound LDX soundChannel,Y ; Set X to the channel number for sound effect Y CPX #3 ; If X < 3 then this sound effect uses just the channel BCC nois1 ; given in X, so jump to nois1 to make the sound effect ; on that channel alone ; If we get here then X = 3 or 4, so we need to make the ; sound effect on two channels: ; ; * If X = 3, use sound channels 0 and 2 ; ; * If X = 4, use sound channels 1 and 2 TYA ; Store the sound effect number on the stack, so we can PHA ; restore it after the call to nois1 below DEX ; Set X = X - 3, so X is now 0 or 1, which is the number DEX ; of the first channel we need to make the sound on DEX ; (i.e. the SQ1 or SQ2 channel) JSR nois1 ; Call nois1 to make the sound effect on channel X, so ; that's the SQ1 or SQ2 channel PLA ; Restore the sound effect number from the stack into Y TAY LDX #2 ; Set X = 2 and fall through into nois1 to make the ; sound effect on the NOISE channel, which is the number ; of the second channel we need to make the sound on .nois1 LDA effectOnSQ1,X ; If the status flag for channel X is zero, then there BEQ nois2 ; is no sound being made on this channel at the moment, ; so jump to nois2 to make the sound LDA soundPriority,Y ; Otherwise set A to the priority of the sound effect we ; want to make CMP channelPriority,X ; If A is less than the priority of the sound currently BCC RTS8 ; being made on channel X, then we mustn't interrupt it ; with our lower-priority sound, so return from the ; subroutine without making the new sound .nois2 ; If we get here then we are cleared to make our new ; sound Y on channel X LDA soundPriority,Y ; Set the priority of the sound on channel X to that of STA channelPriority,X ; our new sound, as we are about to make it SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 TYA ; Set A to the sound number in Y so we can pass it to ; the StartEffect routine ; Fall through into StartEffect_b7 to start making sound ; effect A on channel X
Name: StartEffect_b7 [Show more] Type: Subroutine Category: Sound Summary: Call the StartEffect routine Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * FlushSoundChannel calls StartEffect_b7 * NOISE calls via RTS8

Arguments: A The number of the sound effect to make X The number of the channel on which to make the sound effect
Other entry points: RTS8 Contains an RTS
.StartEffect_b7 JSR StartEffect_b6 ; Call StartEffect to start making sound effect A on ; channel A .RTS8 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: soundChannel [Show more] Type: Variable Category: Sound Summary: The sound channels used by each sound effect Deep dive: Sound effects in NES Elite
Context: See this variable on its own page References: This variable is used as follows: * FlushSpecificSound uses soundChannel * NOISE uses soundChannel

The sound channels used by each sound are defined as follows: * If soundChannel = 0, use the SQ1 sound channel * If soundChannel = 1, use the SQ2 sound channel * If soundChannel = 2, use the NOISE sound channel * If soundChannel = 3, use the SQ1 and NOISE sound channels * If soundChannel = 4, use the SQ2 and NOISE sound channels
.soundChannel EQUB 2 ; Sound 0 EQUB 1 ; Sound 1 EQUB 1 ; Sound 2 EQUB 1 ; Sound 3 EQUB 1 ; Sound 4 EQUB 0 ; Sound 5 EQUB 0 ; Sound 6 EQUB 1 ; Sound 7 EQUB 2 ; Sound 8 EQUB 2 ; Sound 9 EQUB 2 ; Sound 10 EQUB 2 ; Sound 11 EQUB 3 ; Sound 12 EQUB 2 ; Sound 13 EQUB 2 ; Sound 14 EQUB 0 ; Sound 15 EQUB 0 ; Sound 16 EQUB 0 ; Sound 17 EQUB 0 ; Sound 18 EQUB 0 ; Sound 19 EQUB 2 ; Sound 20 EQUB 3 ; Sound 21 EQUB 3 ; Sound 22 EQUB 2 ; Sound 23 EQUB 1 ; Sound 24 EQUB 2 ; Sound 25 EQUB 0 ; Sound 26 EQUB 2 ; Sound 27 EQUB 0 ; Sound 28 EQUB 1 ; Sound 29 EQUB 0 ; Sound 30 EQUB 0 ; Sound 31
Name: soundPriority [Show more] Type: Variable Category: Sound Summary: The default priority for each sound effect Deep dive: Sound effects in NES Elite
Context: See this variable on its own page References: This variable is used as follows: * NOISE uses soundPriority
.soundPriority EQUB 128 ; Sound 0 EQUB 130 ; Sound 1 EQUB 192 ; Sound 2 EQUB 33 ; Sound 3 EQUB 33 ; Sound 4 EQUB 16 ; Sound 5 EQUB 16 ; Sound 6 EQUB 65 ; Sound 7 EQUB 130 ; Sound 8 EQUB 50 ; Sound 9 EQUB 132 ; Sound 10 EQUB 32 ; Sound 11 EQUB 192 ; Sound 12 EQUB 96 ; Sound 13 EQUB 64 ; Sound 14 EQUB 128 ; Sound 15 EQUB 128 ; Sound 16 EQUB 128 ; Sound 17 EQUB 128 ; Sound 18 EQUB 144 ; Sound 19 EQUB 132 ; Sound 20 EQUB 51 ; Sound 21 EQUB 51 ; Sound 22 EQUB 32 ; Sound 23 EQUB 192 ; Sound 24 EQUB 24 ; Sound 25 EQUB 16 ; Sound 26 EQUB 16 ; Sound 27 EQUB 16 ; Sound 28 EQUB 16 ; Sound 29 EQUB 16 ; Sound 30 EQUB 96 ; Sound 31 EQUB 96 ; Sound 32 .SetupPPUForIconBar PHA ; Store the value of A on the stack so we can retrieve ; it below 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 PLA ; Retrieve the value of A from the stack so it is ; unchanged RTS ; Return from the subroutine
Name: GetShipBlueprint [Show more] Type: Subroutine Category: Drawing ships Summary: Fetch a specified byte from the current ship blueprint
Arguments: Y The offset of the byte to return from the blueprint
Returns: A The Y-th byte of the current ship blueprint
.GetShipBlueprint LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank LDA (XX0),Y ; Set A to the Y-th byte of the current ship blueprint ; Fall through into ResetBankA to retrieve the bank ; number we stored above and page it back into memory
Name: ResetBankA [Show more] Type: Subroutine Category: Utility routines Summary: Page a specified bank into memory at $8000 while preserving the value of A Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * GetDefaultNEWB calls ResetBankA

Arguments: Stack The number of the bank to page into memory at $8000
.ResetBankA STA storeA ; Store the value of A so we can retrieve it below PLA ; Fetch the ROM bank number from the stack JSR SetBank ; Page bank A into memory at $8000 LDA storeA ; Restore the value of A that we stored above RTS ; Return from the subroutine
Name: GetDefaultNEWB [Show more] Type: Subroutine Category: Drawing ships Summary: Fetch the default NEWB flags for a specified ship type
Context: See this subroutine on its own page References: This subroutine is called as follows: * NWSHP calls GetDefaultNEWB * TACTICS (Part 4 of 7) calls GetDefaultNEWB

Arguments: Y The ship type
Returns: A The default NEWB flags for ship type Y
.GetDefaultNEWB LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank LDA E%-1,Y ; Set A to the default NEWB flags for ship type Y JMP ResetBankA ; Jump to ResetBankA to retrieve the bank number we ; stored above and page it back into memory, returning ; from the subroutine using a tail call
Name: IncreaseTally [Show more] Type: Subroutine Category: Status Summary: Add the kill count to the fractional and low bytes of our combat rank tally following a kill
Context: See this subroutine on its own page References: This subroutine is called as follows: * EXNO2 calls IncreaseTally

Arguments: X The type of the ship that was killed
Returns: C flag If set, the addition overflowed
.IncreaseTally 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 currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank ; The fractional kill count is taken from the KWL% ; table, according to the ship's type (we look up the ; X-1-th value from KWL% because ship types start at 1 ; rather than 0) LDA KWL%-1,X ; Double the fractional kill count and push the low byte ASL A ; onto the stack PHA LDA KWH%-1,X ; Double the integer kill count and put the high byte ROL A ; in Y TAY PLA ; Add the doubled fractional kill count to our tally, ADC TALLYL ; starting by adding the fractional bytes: STA TALLYL ; ; TALLYL = TALLYL + fractional kill count TYA ; And then we add the low byte of TALLY(1 0): ADC TALLY ; STA TALLY ; TALLY = TALLY + carry + integer kill count ; Fall through into ResetBankP to reset the ROM bank to ; the value we stored on the stack
Name: ResetBankP [Show more] Type: Subroutine Category: Utility routines Summary: Page a specified bank into memory at $8000 while preserving the value of A and the processor flags Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckForPause_b0 calls ResetBankP * CheckPauseButton calls via RTS4 * ChooseMusic_b6 calls via RTS4

Arguments: Stack The number of the bank to page into memory at $8000
Other entry points: RTS4 Contains an RTS
.ResetBankP PLA ; Fetch the ROM bank number from the stack PHP ; Store the processor flags on the stack so we can ; retrieve them below JSR SetBank ; Page bank A into memory at $8000 PLP ; Restore the processor flags, so we return the correct ; Z and N flags for the value of A .RTS4 RTS ; Return from the subroutine
Name: CheckPauseButton [Show more] Type: Subroutine Category: Icon bar Summary: Check whether the pause button has been pressed or an icon bar button has been chosen, and process pause/unpause if required
Context: See this subroutine on its own page References: This subroutine is called as follows: * LL164 calls CheckPauseButton
.CheckPauseButton LDA iconBarChoice ; If iconBarChoice = 0 then the icon bar pointer is over BEQ RTS4 ; a blank button, so jump to RTS4 to return from the ; subroutine ; Otherwise fall through into CheckForPause_b0 to pause ; the game if the pause button is pressed
Name: CheckForPause_b0 [Show more] Type: Subroutine Category: Icon bar Summary: Call the CheckForPause routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckSaveLoadBar calls CheckForPause_b0 * DrawScrollFrames calls CheckForPause_b0

Returns: N, Z flags Set according to the value of A passed to the routine
.CheckForPause_b0 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR CheckForPause ; Call CheckForPause, now that it is paged into memory JMP ResetBankP ; Jump to ResetBankP to retrieve the bank number we ; stored above, page it back into memory and set the ; processor flags according to the value of A, returning ; from the subroutine using a tail call
Name: DrawInventoryIcon [Show more] Type: Subroutine Category: Icon bar Summary: Draw the inventory icon on top of the second button in the icon bar
Context: See this subroutine on its own page References: This subroutine is called as follows: * BuyAndSellCargo calls DrawInventoryIcon * SetIconBarRow calls DrawInventoryIcon
.DrawInventoryIcon ; We draw the inventory icon image from sprites with ; sequential patterns, so first we configure the ; variables to pass to the DrawSpriteImage routine LDA #2 ; Set K = 2, to pass as the number of columns in the STA K ; image to DrawSpriteImage below STA K+1 ; Set K+1 = 2, to pass as the number of rows in the ; image to DrawSpriteImage below LDA #69 ; Set K+2 = 69, so we draw the inventory icon image STA K+2 ; using pattern 69 onwards LDA #8 ; Set K+3 = 8, so we build the image from sprite 8 STA K+3 ; onwards LDA #3 ; Set XC = 3 so we draw the image with the top-left STA XC ; corner in tile column 3 LDA #25 ; Set YC = 25 so we draw the image with the top-left STA YC ; corner on tile row 25 LDX #7 ; Set X = 7 so we draw the image seven pixels into the ; (XC, YC) character block along the x-axis LDY #7 ; Set Y = 7 so we draw the image seven pixels into the ; (XC, YC) character block along the y-axis JMP DrawSpriteImage_b6 ; Draw the inventory icon from sprites, using pattern ; #69 onwards, returning from the subroutine using a ; tail call
Name: MakeSounds_b6 [Show more] Type: Subroutine Category: Sound Summary: Call the MakeSounds routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * MakeSoundsAtVBlank calls MakeSounds_b6 * NMI calls MakeSounds_b6 * SetBank calls MakeSounds_b6
.MakeSounds_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR MakeSounds ; Call MakeSounds, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: ChooseMusic_b6 [Show more] Type: Subroutine Category: Sound Summary: Call the ChooseMusic routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH2 calls ChooseMusic_b6 * DrawTitleScreen calls ChooseMusic_b6 * PauseGame calls ChooseMusic_b6 * TT102 calls ChooseMusic_b6

Arguments: A The number of the tune to choose
.ChooseMusic_b6 PHA ; Wait until the next NMI interrupt has passed (i.e. the JSR WaitForNMI ; next VBlank), preserving the value in A via the stack PLA ORA #%10000000 ; Set bit 7 of the tune number and store in newTune to STA newTune ; indicate that we are now in the process of changing to ; this tune AND #%01111111 ; Clear bit 7 to set A to the tune number once again LDX disableMusic ; If music is disabled then bit 7 of disableMusic will BMI RTS4 ; be set, so jump to RTS4 to return from the subroutine ; as we can't choose a new tune if music is disabled STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 6 is already paged into memory, jump to CMP #6 ; bank1 BEQ bank1 PHA ; Otherwise store the current bank number on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR ChooseMusic ; Call ChooseMusic, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank1 LDA storeA ; Restore the value of A that we stored above JMP ChooseMusic ; Call ChooseMusic, which is already paged into memory, ; and return from the subroutine using a tail call
Name: StartEffect_b6 [Show more] Type: Subroutine Category: Sound Summary: Call the StartEffect routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * StartEffect_b7 calls StartEffect_b6
.StartEffect_b6 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 6 is already paged into memory, jump to CMP #6 ; bank2 BEQ bank2 PHA ; Otherwise store the current bank number on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR StartEffect ; Call StartEffect, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank2 LDA storeA ; Restore the value of A that we stored above JMP StartEffect ; Call StartEffect, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: ResetMusicAfterNMI [Show more] Type: Subroutine Category: Sound Summary: Wait for the next NMI before resetting the current tune to 0 and stopping the music
Context: See this subroutine on its own page References: This subroutine is called as follows: * BR1 calls ResetMusicAfterNMI * DEATH calls ResetMusicAfterNMI * DOKEY calls ResetMusicAfterNMI * DrawTitleScreen calls ResetMusicAfterNMI * Main flight loop (Part 9 of 16) calls ResetMusicAfterNMI * TT102 calls ResetMusicAfterNMI * TT18 calls ResetMusicAfterNMI
.ResetMusicAfterNMI JSR WaitForNMI ; Wait until the next NMI interrupt has passed (i.e. the ; next VBlank) ; Fall through into ResetMusic to reset the current tune ; to 0 and stop the music
Name: ResetMusic [Show more] Type: Subroutine Category: Sound Summary: Reset the current tune to the default and stop all sounds (music and sound effects)
Context: See this subroutine on its own page References: This subroutine is called as follows: * ResetVariables calls ResetMusic * ShowStartScreen calls ResetMusic
.ResetMusic LDA #0 ; Set newTune to select the default tune (tune 0, the STA newTune ; "Elite Theme") and clear bit 7 to indicate we are not ; in the process of changing tunes ; Fall through into StopSounds_b6 to stop all sounds ; (music and sound effects)
Name: StopSounds_b6 [Show more] Type: Subroutine Category: Sound Summary: Call the StopSounds routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * PauseGame calls StopSounds_b6
.StopSounds_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR StopSoundsS ; Call StopSounds via StopSoundsS, now that it is paged ; into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: SetDemoAutoPlay_b5 [Show more] Type: Subroutine Category: Combat demo Summary: Call the SetDemoAutoPlay routine in ROM bank 5 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChooseLanguage calls SetDemoAutoPlay_b5
.SetDemoAutoPlay_b5 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #5 ; Page ROM bank 5 into memory at $8000 JSR SetBank JSR SetDemoAutoPlay ; Call SetDemoAutoPlay, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DrawSmallLogo_b4 [Show more] Type: Subroutine Category: Save and load Summary: Call the DrawSmallLogo routine in ROM bank 4 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * SVE calls DrawSmallLogo_b4
.DrawSmallLogo_b4 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #4 ; Page ROM bank 4 into memory at $8000 JSR SetBank JSR DrawSmallLogo ; Call DrawSmallLogo, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DrawBigLogo_b4 [Show more] Type: Subroutine Category: Start and end Summary: Call the DrawBigLogo routine in ROM bank 4 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChooseLanguage calls DrawBigLogo_b4
.DrawBigLogo_b4 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #4 ; Page ROM bank 4 into memory at $8000 JSR SetBank JSR DrawBigLogo ; Call DrawBigLogo, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: FadeToBlack_b3 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the FadeToBlack routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRP calls FadeToBlack_b3 * DEATH2 calls FadeToBlack_b3 * DrawTitleScreen calls FadeToBlack_b3 * FadeAndHideSprites calls FadeToBlack_b3 * ShowScrollText calls FadeToBlack_b3
.FadeToBlack_b3 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR FadeToBlack ; Call FadeToBlack, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: CheckSaveSlots_b6 [Show more] Type: Subroutine Category: Save and load Summary: Call the CheckSaveSlots routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChooseLanguage calls CheckSaveSlots_b6
.CheckSaveSlots_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR CheckSaveSlots ; Call CheckSaveSlots, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: LL9_b1 [Show more] Type: Subroutine Category: Drawing ships Summary: Call the LL9 routine in ROM bank 1 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawShipInBitplane calls LL9_b1 * Main flight loop (Part 12 of 16) calls LL9_b1
.LL9_b1 LDA currentBank ; If ROM bank 1 is already paged into memory, jump to CMP #1 ; bank3 BEQ bank3 PHA ; Otherwise store the current bank number on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR LL9 ; Call LL9, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank3 JMP LL9 ; Call LL9, which is already paged into memory, and ; return from the subroutine using a tail call
Name: SIGHT_b3 [Show more] Type: Subroutine Category: Flight Summary: Call the SIGHT routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * PlayDemo calls SIGHT_b3 * ResetStardust calls SIGHT_b3
.SIGHT_b3 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR SIGHT ; Call SIGHT, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: TIDY_b1 [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Call the TIDY routine in ROM bank 1 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * MVEIT (Part 1 of 9) calls TIDY_b1
.TIDY_b1 LDA currentBank ; If ROM bank 1 is already paged into memory, jump to CMP #1 ; bank4 BEQ bank4 PHA ; Otherwise store the current bank number on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR TIDY ; Call TIDY, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank4 JMP TIDY ; Call TIDY, which is already paged into memory, and ; return from the subroutine using a tail call
Name: ChooseLanguage_b6 [Show more] Type: Subroutine Category: Start and end Summary: Call the ChooseLanguage routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ShowStartScreen calls ChooseLanguage_b6
.ChooseLanguage_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR ChooseLanguage ; Call ChooseLanguage, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: PlayDemo_b0 [Show more] Type: Subroutine Category: Combat demo Summary: Call the PlayDemo routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ShowScrollText calls PlayDemo_b0
.PlayDemo_b0 LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JMP PlayDemo ; Call PlayDemo, which is already paged into memory, and ; return from the subroutine using a tail call
Name: STARS_b1 [Show more] Type: Subroutine Category: Stardust Summary: Call the STARS routine in ROM bank 1 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 16 of 16) calls STARS_b1 * ShowScrollText calls STARS_b1
.STARS_b1 LDA currentBank ; If ROM bank 1 is already paged into memory, jump to CMP #1 ; bank5 BEQ bank5 PHA ; Otherwise store the current bank number on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR STARS ; Call STARS, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank5 JMP STARS ; Call STARS, which is already paged into memory, and ; return from the subroutine using a tail call
Name: CIRCLE2_b1 [Show more] Type: Subroutine Category: Drawing circles Summary: Call the CIRCLE2 routine in ROM bank 1 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT128 calls CIRCLE2_b1
.CIRCLE2_b1 LDA currentBank ; If ROM bank 1 is already paged into memory, jump to CMP #1 ; bank6 BEQ bank6 PHA ; Otherwise store the current bank number on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR CIRCLE2 ; Call CIRCLE2, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank6 JMP CIRCLE2 ; Call CIRCLE2, which is already paged into memory, and ; return from the subroutine using a tail call
Name: SUN_b1 [Show more] Type: Subroutine Category: Drawing suns Summary: Call the SUN routine in ROM bank 1 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
.SUN_b1 LDA currentBank ; If ROM bank 1 is already paged into memory, jump to CMP #1 ; bank7 BEQ bank7 PHA ; Otherwise store the current bank number on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR SUN ; Call SUN, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank7 JMP SUN ; Call SUN, which is already paged into memory, and ; return from the subroutine using a tail call
Name: DrawBackground_b3 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the DrawBackground routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawCmdrImage calls DrawBackground_b3 * DrawSystemImage calls DrawBackground_b3
.DrawBackground_b3 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR DrawBackground ; Call DrawBackground, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DrawSystemImage_b3 [Show more] Type: Subroutine Category: Universe Summary: Call the DrawSystemImage routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT25 calls DrawSystemImage_b3
.DrawSystemImage_b3 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank8 BEQ bank8 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR DrawSystemImage ; Call DrawSystemImage, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank8 LDA storeA ; Restore the value of A that we stored above JMP DrawSystemImage ; Call DrawSystemImage, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: DrawImageNames_b4 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the DrawImageNames routine in ROM bank 4 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawCobraMkIII calls DrawImageNames_b4
.DrawImageNames_b4 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #4 ; Page ROM bank 4 into memory at $8000 JSR SetBank JSR DrawImageNames ; Call DrawImageNames, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DrawCmdrImage_b6 [Show more] Type: Subroutine Category: Status Summary: Call the DrawCmdrImage routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * STATUS calls DrawCmdrImage_b6
.DrawCmdrImage_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR DrawCmdrImage ; Call DrawCmdrImage, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DrawSpriteImage_b6 [Show more] Type: Subroutine Category: Drawing sprites Summary: Call the DrawSpriteImage routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawCmdrImage calls DrawSpriteImage_b6 * DrawInventoryIcon calls DrawSpriteImage_b6 * DrawSystemImage calls DrawSpriteImage_b6
.DrawSpriteImage_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR DrawSpriteImage ; Call DrawSpriteImage, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: GetHeadshotType_b4 [Show more] Type: Subroutine Category: Status Summary: Call the GetHeadshotType routine in ROM bank 4 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * STATUS calls GetHeadshotType_b4
.GetHeadshotType_b4 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #4 ; Page ROM bank 4 into memory at $8000 JSR SetBank JSR GetHeadshotType ; Call GetHeadshotType, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DrawEquipment_b6 [Show more] Type: Subroutine Category: Equipment Summary: Call the DrawEquipment routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawCobraMkIII calls DrawEquipment_b6 * EQSHP calls DrawEquipment_b6 * UpdateEquipment calls DrawEquipment_b6
.DrawEquipment_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR DrawEquipment ; Call DrawEquipment, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DEATH2_b0 [Show more] Type: Subroutine Category: Start and end Summary: Switch to ROM bank 0 and call the DEATH2 routine Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * PauseGame calls DEATH2_b0
.DEATH2_b0 LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JMP DEATH2 ; Call DEATH2, which is now paged into memory, and ; return from the subroutine using a tail call
Name: StartGame_b0 [Show more] Type: Subroutine Category: Start and end Summary: Switch to ROM bank 0 and call the StartGame routine Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ShowScrollText calls StartGame_b0
.StartGame_b0 LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JMP StartGame ; Call StartGame, which is now paged into memory, and ; return from the subroutine using a tail call
Name: SetViewAttrs_b3 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the SetViewAttrs routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT66 calls SetViewAttrs_b3
.SetViewAttrs_b3 LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank9 BEQ bank9 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR SetViewAttrs ; Call SetViewAttrs, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank9 JMP SetViewAttrs ; Call SetViewAttrs, which is already paged into memory, ; and return from the subroutine using a tail call
Name: FadeToColour_b3 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the FadeToColour routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendViewToPPU calls FadeToColour_b3 * SetupViewInNMI calls FadeToColour_b3
.FadeToColour_b3 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR FadeToColour ; Call FadeToColour, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DrawSmallBox_b3 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the DrawSmallBox routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * qv calls DrawSmallBox_b3 * TT22 calls DrawSmallBox_b3
.DrawSmallBox_b3 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR DrawSmallBox ; Call DrawSmallBox, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DrawImageFrame_b3 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the DrawImageFrame routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawCmdrImage calls DrawImageFrame_b3
.DrawImageFrame_b3 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR DrawImageFrame ; Call DrawImageFrame, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DrawLaunchBox_b6 [Show more] Type: Subroutine Category: Flight Summary: Call the DrawLaunchBox routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * LAUN calls DrawLaunchBox_b6
.DrawLaunchBox_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR DrawLaunchBox ; Call DrawLaunchBox, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: SetLinePatterns_b3 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the SetLinePatterns routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ShowScrollText calls SetLinePatterns_b3 * TT66 calls SetLinePatterns_b3
.SetLinePatterns_b3 LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank10 BEQ bank10 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR SetLinePatterns ; Call SetLinePatterns, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank10 JMP SetLinePatterns ; Call SetLinePatterns, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: TT24_b6 [Show more] Type: Subroutine Category: Universe Summary: Call the TT24 routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * hyp1 calls TT24_b6 * TT111 calls TT24_b6
.TT24_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR TT24 ; Call TT24, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: ClearDashEdge_b6 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the ClearDashEdge routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH calls ClearDashEdge_b6
.ClearDashEdge_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR ClearDashEdge ; Call ClearDashEdge, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: LoadNormalFont_b3 [Show more] Type: Subroutine Category: Text Summary: Call the LoadNormalFont routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT66 calls LoadNormalFont_b3
.LoadNormalFont_b3 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank11 BEQ bank11 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR LoadNormalFont ; Call LoadNormalFont, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank11 LDA storeA ; Restore the value of A that we stored above JMP LoadNormalFont ; Call LoadNormalFont, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: LoadHighFont_b3 [Show more] Type: Subroutine Category: Text Summary: Call the LoadHighFont routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT66 calls LoadHighFont_b3
.LoadHighFont_b3 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR LoadHighFont ; Call LoadHighFont, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: PAS1_b0 [Show more] Type: Subroutine Category: Missions Summary: Call the PAS1 routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * PAUSE calls PAS1_b0
.PAS1_b0 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JSR PAS1 ; Call PAS1, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: GetSystemImage_b5 [Show more] Type: Subroutine Category: Universe Summary: Call the GetSystemImage routine in ROM bank 5 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendViewToPPU calls GetSystemImage_b5
.GetSystemImage_b5 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #5 ; Page ROM bank 5 into memory at $8000 JSR SetBank JSR GetSystemImage ; Call GetSystemImage, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: GetSystemBack_b5 [Show more] Type: Subroutine Category: Universe Summary: Call the GetSystemBack routine in ROM bank 5 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * SetupViewInNMI calls GetSystemBack_b5
.GetSystemBack_b5 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #5 ; Page ROM bank 5 into memory at $8000 JSR SetBank JSR GetSystemBack ; Call GetSystemBack, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: GetCmdrImage_b4 [Show more] Type: Subroutine Category: Status Summary: Call the GetCmdrImage routine in ROM bank 4 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * SendViewToPPU calls GetCmdrImage_b4
.GetCmdrImage_b4 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #4 ; Page ROM bank 4 into memory at $8000 JSR SetBank JSR GetCmdrImage ; Call GetCmdrImage, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: GetHeadshot_b4 [Show more] Type: Subroutine Category: Status Summary: Call the GetHeadshot routine in ROM bank 4 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * SetupViewInNMI calls GetHeadshot_b4
.GetHeadshot_b4 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #4 ; Page ROM bank 4 into memory at $8000 JSR SetBank JSR GetHeadshot ; Call GetHeadshot, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DIALS_b6 [Show more] Type: Subroutine Category: Dashboard Summary: Call the DIALS routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawSpaceViewInNMI calls DIALS_b6
.DIALS_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR DIALS ; Call DIALS, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: InputName_b6 [Show more] Type: Subroutine Category: Controllers Summary: Call the InputName routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * HME2 calls InputName_b6
.InputName_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR InputName ; Call InputName, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: ChangeToView_b0 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the ChangeToView routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ShowScrollText calls ChangeToView_b0
.ChangeToView_b0 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 0 is already paged into memory, jump to CMP #0 ; bank12 BEQ bank12 PHA ; Otherwise store the current bank number on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR ChangeToView ; Call ChangeToView, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank12 LDA storeA ; Restore the value of A that we stored above JMP ChangeToView ; Call ChangeToView, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: LL164_b6 [Show more] Type: Subroutine Category: Flight Summary: Call the LL164 routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT18 calls LL164_b6
.LL164_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR LL164 ; Call LL164, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DrawLightning_b6 [Show more] Type: Subroutine Category: Flight Summary: Call the DrawLightning routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * FlightLoop4To16 calls DrawLightning_b6 * LAUN calls DrawLightning_b6
.DrawLightning_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR DrawLightning ; Call DrawLightning, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: PauseGame_b6 [Show more] Type: Subroutine Category: Icon bar Summary: Call the PauseGame routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckForPause calls PauseGame_b6 * DOKEY calls PauseGame_b6 * qv calls PauseGame_b6
.PauseGame_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR PauseGame ; Call PauseGame, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: SetKeyLogger_b6 [Show more] Type: Subroutine Category: Controllers Summary: Call the SetKeyLogger routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOKEY calls SetKeyLogger_b6 * PAS1 calls SetKeyLogger_b6 * PauseGame calls SetKeyLogger_b6
.SetKeyLogger_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR SetKeyLogger ; Call SetKeyLogger, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: ChangeCmdrName_b6 [Show more] Type: Subroutine Category: Save and load Summary: Call the ChangeCmdrName routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * CheckSaveLoadBar calls ChangeCmdrName_b6
.ChangeCmdrName_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR ChangeCmdrName ; Call ChangeCmdrName, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: ResetCommander_b6 [Show more] Type: Subroutine Category: Save and load Summary: Call the ResetCommander routine in ROM bank 6
Context: See this subroutine on its own page References: This subroutine is called as follows: * BR1 calls ResetCommander_b6 * PlayDemo calls ResetCommander_b6
.ResetCommander_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR ResetCommander ; Call ResetCommander, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: JAMESON_b6 [Show more] Type: Subroutine Category: Save and load Summary: Call the JAMESON routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ShowStartScreen calls JAMESON_b6
.JAMESON_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR JAMESON ; Call JAMESON, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: ShowScrollText_b6 [Show more] Type: Subroutine Category: Combat demo Summary: Call the ShowScrollText routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH2 calls ShowScrollText_b6 * Main flight loop (Part 15 of 16) calls ShowScrollText_b6
.ShowScrollText_b6 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 6 is already paged into memory, jump to CMP #6 ; bank13 BEQ bank13 PHA ; Otherwise store the current bank number on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR ShowScrollText ; Call ShowScrollText, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank13 LDA storeA ; Restore the value of A that we stored above JMP ShowScrollText ; Call ShowScrollText, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: BEEP_b7 [Show more] Type: Subroutine Category: Sound Summary: Call the BEEP routine in ROM bank 7 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * EQSHP calls BEEP_b7 * InputName calls BEEP_b7 * Main flight loop (Part 11 of 16) calls BEEP_b7 * refund calls BEEP_b7
.BEEP_b7 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JSR BEEP ; Call BEEP, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: DETOK_b2 [Show more] Type: Subroutine Category: Text Summary: Call the DETOK routine in ROM bank 2 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIEF2 calls DETOK_b2 * BRIS_b0 calls DETOK_b2 * BRP calls DETOK_b2 * ChangeCmdrName calls DETOK_b2 * ChooseLanguage calls DETOK_b2 * dockEd calls DETOK_b2 * HME2 calls DETOK_b2 * MT17 calls DETOK_b2 * MT28 calls DETOK_b2 * STATUS calls DETOK_b2 * TBRIEF calls DETOK_b2 * TT210 calls DETOK_b2 * YESNO calls DETOK_b2
.DETOK_b2 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 2 is already paged into memory, jump to CMP #2 ; bank14 BEQ bank14 PHA ; Otherwise store the current bank number on the stack LDA #2 ; Page ROM bank 2 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR DETOK ; Call DETOK, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank14 LDA storeA ; Restore the value of A that we stored above JMP DETOK ; Call DETOK, which is already paged into memory, and ; return from the subroutine using a tail call
Name: DTS_b2 [Show more] Type: Subroutine Category: Text Summary: Call the DTS routine in ROM bank 2 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * MT18 calls DTS_b2
.DTS_b2 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 2 is already paged into memory, jump to CMP #2 ; bank15 BEQ bank15 PHA ; Otherwise store the current bank number on the stack LDA #2 ; Page ROM bank 2 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR DTS ; Call DTS, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank15 LDA storeA ; Restore the value of A that we stored above JMP DTS ; Call DTS, which is already paged into memory, and ; return from the subroutine using a tail call
Name: PDESC_b2 [Show more] Type: Subroutine Category: Text Summary: Call the PDESC routine in ROM bank 2 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT25 calls PDESC_b2
.PDESC_b2 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #2 ; Page ROM bank 2 into memory at $8000 JSR SetBank JSR PDESC ; Call PDESC, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: SetupIconBar_b3 [Show more] Type: Subroutine Category: Icon bar Summary: Call the SetupIconBar routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT66 calls SetupIconBar_b3
.SetupIconBar_b3 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank16 BEQ bank16 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR SetupIconBar ; Call SetupIconBar, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank16 LDA storeA ; Restore the value of A that we stored above JMP SetupIconBar ; Call SetupIconBar, which is already paged into memory, ; and return from the subroutine using a tail call
Name: ShowIconBar_b3 [Show more] Type: Subroutine Category: Icon bar Summary: Call the ShowIconBar routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * PauseGame calls ShowIconBar_b3
.ShowIconBar_b3 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank17 BEQ bank17 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR ShowIconBar ; Call ShowIconBar, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank17 LDA storeA ; Restore the value of A that we stored above JMP ShowIconBar ; Call ShowIconBar, which is already paged into memory, ; and return from the subroutine using a tail call
Name: DrawDashNames_b3 [Show more] Type: Subroutine Category: Dashboard Summary: Call the DrawDashNames routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT66 calls DrawDashNames_b3
.DrawDashNames_b3 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR DrawDashNames ; Call DrawDashNames, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: ResetScanner_b3 [Show more] Type: Subroutine Category: Dashboard Summary: Call the ResetScanner routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT66 calls ResetScanner_b3
.ResetScanner_b3 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR ResetScanner ; Call ResetScanner, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: ResetScreen_b3 [Show more] Type: Subroutine Category: Start and end Summary: Call the ResetScreen routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ResetVariables calls ResetScreen_b3
.ResetScreen_b3 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR ResetScreen ; Call ResetScreen, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: UpdateScreen [Show more] Type: Subroutine Category: PPU Summary: Update the screen by sending data to the PPU, either immediately or during VBlank, depending on whether the screen is visible Deep dive: Views and view types in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChangeToView calls UpdateScreen * UpdateHangarView calls UpdateScreen * UpdateView calls UpdateScreen
.UpdateScreen LDA screenFadedToBlack ; If bit 7 of screenFadedToBlack is clear then the BPL SetupFullViewInNMI ; screen is visible and has not been faded to black, so ; we need to send the view to the PPU in the NMI handler ; to avoid corrupting the screen, so jump to ; SetupFullViewInNMI to configure the NMI handler ; accordingly ; Otherwise the screen has been faded to black, so we ; can fall through into SendViewToPPU to send the view ; straight to the PPU without having to restrict ; ourselves to VBlank
Name: SendViewToPPU_b3 [Show more] Type: Subroutine Category: PPU Summary: Call the SendViewToPPU routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DEATH calls SendViewToPPU_b3 * SendSpaceViewToPPU calls SendViewToPPU_b3 * UpdateView calls SendViewToPPU_b3
.SendViewToPPU_b3 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR SendViewToPPU ; Call SendViewToPPU, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: SetupFullViewInNMI [Show more] Type: Subroutine Category: Drawing the screen Summary: Configure the PPU to send tiles for the full screen during VBlank
Context: See this subroutine on its own page References: This subroutine is called as follows: * PlayDemo calls SetupFullViewInNMI * UpdateScreen calls SetupFullViewInNMI
.SetupFullViewInNMI LDA #116 ; Tell the PPU to send nametable entries up to tile STA lastNameTile ; 116 * 8 = 928 (i.e. to the end of tile row 28) in both STA lastNameTile+1 ; bitplanes ; Fall through into SetupViewInNMI_b3 to setup the view ; and configure the NMI to send both bitplanes to the ; PPU during VBlank
Name: SetupViewInNMI_b3 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the SetupViewInNMI routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * SetSpaceViewInNMI calls SetupViewInNMI_b3
.SetupViewInNMI_b3 LDA #%11000000 ; Set A to the bitplane flags to set for the drawing ; bitplane in the call to SetupViewInNMI below: ; ; * Bit 2 clear = send tiles up to configured numbers ; * Bit 3 clear = don't clear buffers after sending ; * Bit 4 clear = we've not started sending data yet ; * Bit 5 clear = we have not yet sent all the data ; * Bit 6 set = send both pattern and nametable data ; * Bit 7 set = send data to the PPU ; ; Bits 0 and 1 are ignored and are always clear STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank18 BEQ bank18 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR SetupViewInNMI ; Call SetupViewInNMI, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank18 LDA storeA ; Restore the value of A that we stored above JMP SetupViewInNMI ; Call SetupViewInNMI, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: SendBitplaneToPPU_b3 [Show more] Type: Subroutine Category: PPU Summary: Call the SendBitplaneToPPU routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file
.SendBitplaneToPPU_b3 LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank19 BEQ bank19 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR SendBitplaneToPPU ; Call SendBitplaneToPPU, now that it is paged into ; memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank19 JMP SendBitplaneToPPU ; Call SendBitplaneToPPU, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: UpdateIconBar_b3 [Show more] Type: Subroutine Category: Icon bar Summary: Call the UpdateIconBar routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
.UpdateIconBar_b3 LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank20 BEQ bank20 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR UpdateIconBar ; Call UpdateIconBar, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank20 JMP UpdateIconBar ; Call UpdateIconBar, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: DrawScreenInNMI_b0 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the DrawScreenInNMI routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * PAUSE calls DrawScreenInNMI_b0 * PAUSE2 calls DrawScreenInNMI_b0 * UpdateSaveScreen calls DrawScreenInNMI_b0
.DrawScreenInNMI_b0 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JSR DrawScreenInNMI ; Call DrawScreenInNMI, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: SVE_b6 [Show more] Type: Subroutine Category: Save and load Summary: Call the SVE routine in ROM bank 6 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT102 calls SVE_b6
.SVE_b6 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #6 ; Page ROM bank 6 into memory at $8000 JSR SetBank JSR SVE ; Call SVE, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: MVS5_b0 [Show more] Type: Subroutine Category: Moving Summary: Call the MVS5 routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * HAS1 calls MVS5_b0
.MVS5_b0 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 0 is already paged into memory, jump to CMP #0 ; bank21 BEQ bank21 PHA ; Otherwise store the current bank number on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR MVS5 ; Call MVS5, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank21 LDA storeA ; Restore the value of A that we stored above JMP MVS5 ; Call MVS5, which is already paged into memory, and ; return from the subroutine using a tail call
Name: HALL_b1 [Show more] Type: Subroutine Category: Ship hangar Summary: Call the HALL routine in ROM bank 1 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOENTRY calls HALL_b1
.HALL_b1 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR HALL ; Call HALL, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: CHPR_b2 [Show more] Type: Subroutine Category: Text Summary: Call the CHPR routine in ROM bank 2 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChangeLetter calls CHPR_b2 * InputName calls CHPR_b2 * PrintFlightMessage calls CHPR_b2
.CHPR_b2 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 2 is already paged into memory, jump to CMP #2 ; bank22 BEQ bank22 PHA ; Otherwise store the current bank number on the stack LDA #2 ; Page ROM bank 2 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR CHPR ; Call CHPR, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank22 LDA storeA ; Restore the value of A that we stored above JMP CHPR ; Call CHPR, which is already paged into memory, and ; return from the subroutine using a tail call
Name: DASC_b2 [Show more] Type: Subroutine Category: Text Summary: Call the DASC routine in ROM bank 2 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * BPRNT calls DASC_b2 * cmn calls DASC_b2 * PrintCharacter calls DASC_b2 * PrintCommanderName calls DASC_b2 * SetSelectedSystem calls DASC_b2 * TT160 calls DASC_b2 * TT161 calls DASC_b2 * TT16a calls DASC_b2 * TT210 calls DASC_b2 * TT25 calls DASC_b2
.DASC_b2 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 2 is already paged into memory, jump to CMP #2 ; bank23 BEQ bank23 PHA ; Otherwise store the current bank number on the stack LDA #2 ; Page ROM bank 2 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR DASC ; Call DASC, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank23 LDA storeA ; Restore the value of A that we stored above JMP DASC ; Call DASC, which is already paged into memory, and ; return from the subroutine using a tail call
Name: TT27_b2 [Show more] Type: Subroutine Category: Text Summary: Call the TT27 routine in ROM bank 2 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * cpl calls TT27_b2 * csh calls TT27_b2 * EQSHP calls TT27_b2 * mes9 calls TT27_b2 * MESS calls TT27_b2 * NLIN3 calls TT27_b2 * plf calls TT27_b2 * PrintEquipment calls TT27_b2 * PrintLaserView calls TT27_b2 * PrintSaveHeader calls TT27_b2 * PrintSpaceAndToken calls TT27_b2 * PrintSpacedHyphen calls TT27_b2 * PrintSpaceViewName calls TT27_b2 * PrintTokenAndColon calls TT27_b2 * PrintTokenCrTab calls TT27_b2 * prq calls TT27_b2 * spc calls TT27_b2 * STATUS calls TT27_b2 * TT147 calls TT27_b2 * TT151 calls TT27_b2 * TT162 calls TT27_b2 * TT210 calls TT27_b2 * TT213 calls TT27_b2 * TT25 calls TT27_b2 * TT60 calls TT27_b2 * TT66 calls TT27_b2 * TT67 calls TT27_b2 * TT68 calls TT27_b2 * TT70 calls TT27_b2 * TT73 calls TT27_b2
.TT27_b2 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 2 is already paged into memory, jump to CMP #2 ; bank24 BEQ bank24 PHA ; Otherwise store the current bank number on the stack LDA #2 ; Page ROM bank 2 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR TT27 ; Call TT27, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank24 LDA storeA ; Restore the value of A that we stored above JMP TT27 ; Call TT27, which is already paged into memory, and ; return from the subroutine using a tail call
Name: ex_b2 [Show more] Type: Subroutine Category: Text Summary: Call the ex routine in ROM bank 2 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * PrintMessage calls ex_b2
.ex_b2 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; If ROM bank 2 is already paged into memory, jump to CMP #2 ; bank25 BEQ bank25 PHA ; Otherwise store the current bank number on the stack LDA #2 ; Page ROM bank 2 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR ex ; Call ex, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank25 LDA storeA ; Restore the value of A that we stored above JMP ex ; Call ex, which is already paged into memory, and ; return from the subroutine using a tail call
Name: PrintCtrlCode_b0 [Show more] Type: Subroutine Category: Text Summary: Call the PrintCtrlCode routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * TT27 calls PrintCtrlCode_b0
.PrintCtrlCode_b0 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JSR PrintCtrlCode ; Call PrintCtrlCode, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: SetupAfterLoad_b0 [Show more] Type: Subroutine Category: Start and end Summary: Call the SetupAfterLoad routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * SaveLoadCommander calls SetupAfterLoad_b0
.SetupAfterLoad_b0 LDA currentBank ; If ROM bank 0 is already paged into memory, jump to CMP #0 ; bank26 BEQ bank26 PHA ; Otherwise store the current bank number on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JSR SetupAfterLoad ; Call SetupAfterLoad, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank26 JMP SetupAfterLoad ; Call SetupAfterLoad, which is already paged into ; memory, and return from the subroutine using a tail ; call
Name: HideShip_b1 [Show more] Type: Subroutine Category: Dashboard Summary: Update the current ship so it is no longer shown on the scanner Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * Main flight loop (Part 11 of 16) calls HideShip_b1
.HideShip_b1 LDA #0 ; Zero byte #33 in the current ship's data block at K%, LDY #33 ; so it is not shown on the scanner (a non-zero byte #33 STA (INF),Y ; represents the ship's number on the scanner, with a ; ship number of zero indicating that the ship is not ; shown on the scanner) ; Fall through into HideFromScanner to hide the scanner ; sprites for this ship and reset byte #33 in the INWK ; workspace
Name: HideFromScanner_b1 [Show more] Type: Subroutine Category: Dashboard Summary: Call the HideFromScanner routine in ROM bank 1 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIEF calls HideFromScanner_b1 * KILLSHP calls HideFromScanner_b1 * TITLE calls HideFromScanner_b1
.HideFromScanner_b1 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR HideFromScanner ; Call HideFromScanner, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: TT66_b0 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the TT66 routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChooseLanguage calls TT66_b0 * HALL calls TT66_b0 * MT9 calls TT66_b0 * PAUSE calls TT66_b0 * SVE calls TT66_b0
.TT66_b0 STA storeA ; Store the value of A so we can retrieve it below LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank LDA storeA ; Restore the value of A that we stored above JSR TT66 ; Call TT66, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: CLIP_b1 [Show more] Type: Subroutine Category: Drawing lines Summary: Call the CLIP routine in ROM bank 1, drawing the clipped line if it fits on-screen Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * DrawScrollFrame calls CLIP_b1
.CLIP_b1 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR CLIP ; Call CLIP, now that it is paged into memory BCS P%+5 ; If the C flag is set then the clipped line does not ; fit on-screen, so skip the next instruction JSR LOIN ; The clipped line fits on-screen, so draw it JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: ClearScreen_b3 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the ClearScreen routine in ROM bank 3 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * BAY calls ClearScreen_b3 * TBRIEF calls ClearScreen_b3 * TT66 calls ClearScreen_b3
.ClearScreen_b3 LDA currentBank ; If ROM bank 3 is already paged into memory, jump to CMP #3 ; bank27 BEQ bank27 PHA ; Otherwise store the current bank number on the stack LDA #3 ; Page ROM bank 3 into memory at $8000 JSR SetBank JSR ClearScreen ; Call ClearScreen, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank27 JMP ClearScreen ; Call ClearScreen, which is already paged into memory, ; and return from the subroutine using a tail call
Name: SCAN_b1 [Show more] Type: Subroutine Category: Dashboard Summary: Call the SCAN routine in ROM bank 1 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * MVEIT (Part 9 of 9) calls SCAN_b1
.SCAN_b1 LDA currentBank ; If ROM bank 1 is already paged into memory, jump to CMP #1 ; bank28 BEQ bank28 PHA ; Otherwise store the current bank number on the stack LDA #1 ; Page ROM bank 1 into memory at $8000 JSR SetBank JSR SCAN ; Call SCAN, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call .bank28 JMP SCAN ; Call SCAN, which is already paged into memory, and ; return from the subroutine using a tail call
Name: UpdateViewWithFade [Show more] Type: Subroutine Category: Drawing the screen Summary: Fade the screen to black, if required, hide all sprites and update the view
Context: See this subroutine on its own page References: This subroutine is called as follows: * BRIS calls UpdateViewWithFade * BRIS_b0 calls UpdateViewWithFade * TT210 calls UpdateViewWithFade
.UpdateViewWithFade JSR SetScreenForUpdate ; Get the screen ready for updating by hiding all ; sprites, after fading the screen to black if we are ; changing view ; Fall through into UpdateView to update the view
Name: UpdateView_b0 [Show more] Type: Subroutine Category: Drawing the screen Summary: Call the UpdateView routine in ROM bank 0 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChooseLanguage calls UpdateView_b0 * SVE calls UpdateView_b0
.UpdateView_b0 LDA currentBank ; Fetch the number of the ROM bank that is currently PHA ; paged into memory at $8000 and store it on the stack LDA #0 ; Page ROM bank 0 into memory at $8000 JSR SetBank JSR UpdateView ; Call UpdateView, now that it is paged into memory JMP ResetBank ; Fetch the previous ROM bank number from the stack and ; page that bank back into memory at $8000, returning ; from the subroutine using a tail call
Name: UpdateHangarView [Show more] Type: Subroutine Category: PPU Summary: Update the hangar view on-screen by sending the data to the PPU, either immediately or during VBlank Deep dive: Views and view types in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * HALL calls UpdateHangarView
.UpdateHangarView LDA #0 ; Page ROM bank 0 into memory at $8000 (this isn't JSR SetBank ; strictly necessarily as this routine gets jumped to ; from the end of the HALL routine in bank 1, which ; itself is only called via HALL_b1, so the latter will ; revert to bank 0 following the RTS below and none of ; the following calls are to bank 0) JSR CopyNameBuffer0To1 ; Copy the contents of nametable buffer 0 to nametable ; buffer JSR UpdateScreen ; Update the screen by sending data to the PPU, either ; immediately or during VBlank, depending on whether ; the screen is visible LDX #1 ; Hide bitplane 1, so: STX hiddenBitplane ; ; * Colour %01 (1) is the visible colour (cyan) ; * Colour %10 (2) is the hidden colour (black) RTS ; Return from the subroutine
Name: CLYNS [Show more] Type: Subroutine Category: Drawing the screen Summary: Clear the bottom two text rows of the visible screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * ChangeCmdrName calls CLYNS * dockEd calls CLYNS * FlightLoop4To16 calls CLYNS * HME2 calls CLYNS * JMTB calls CLYNS * SetSelectedSystem calls CLYNS * TT102 calls CLYNS * TT147 calls CLYNS * YESNO calls CLYNS * PrintFlightMessage calls via CLYNS+8

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

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

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

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

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

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

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

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

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

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

Calculate the distance from the origin to the point (x, y, z) and compare it with the argument A, clearing the C flag if the distance is < A, or setting the C flag if the distance is >= A. This routine does a similar job to the routine of the same name in the BBC Master version of Elite, but the code is significantly different and the result is returned with the C flag the other way around. The algorithm actually calculates the distance as 0.5 * |x y z|, using an approximation that that estimates the length within 8% of the correct value, and without having to do any multiplication or take any square roots. If h is the longest component of x, y, z, and a and b are the other two sides, then the algorithm is as follows: 0.5 * |(x y z)| ~= (5 * a + 5 * b + 16 * h) / 32 which we calculate like this: 5/32 * a + 5/32 * b + 1/2 * h Calculating half the distance to the point (i.e. 0.5 * |x y z|) ensures that the result fits into one byte. The distance to check against in A is not halved, so the comparison ends up being between |(x y z)| and A * 2.
Arguments: A The distance to check against (the distance is checked against A * 2)
Returns: C flag The result of comparing |x y z| with A: * Clear if the distance to (x, y, z) < A * 2 * Set if the distance to (x, y, z) >= A * 2
.FAROF2 STA T ; Store the value that we want to compare x, y z with ; in T SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA INWK+2 ; If any of x_sign, y_sign or z_sign are non-zero ORA INWK+5 ; (ignoring the sign in bit 7), then jump to farr3 to ORA INWK+8 ; return the C flag set, to indicate that A is smaller ASL A ; than x, y, z BNE farr3 LDA INWK+7 ; Set K+2 = z_hi / 2 LSR A STA K+2 LDA INWK+1 ; Set K = x_hi / 2 LSR A STA K LDA INWK+4 ; Set K+1 = y_hi / 2 LSR A ; STA K+1 ; This also sets A = K+1 ; From this point on we are only working with the high ; bytes, so to make things easier to follow, let's just ; refer to x_hi, y_hi and z_hi as x, y and z, so: ; ; K = x / 2 ; K+1 = y / 2 ; K+2 = z / 2 CMP K ; If A >= K, jump to farr1 to skip the next instruction BCS farr1 LDA K ; Set A = K, so A = max(K, K+1) .farr1 CMP K+2 ; If A >= K+2, jump to farr2 to skip the next BCS farr2 ; instruction LDA K+2 ; Set A = K+2, so A = max(A, K+2) ; = max(K, K+1, K+2) .farr2 STA SC ; Set SC = A ; = max(K, K+1, K+2) ; = max(x / 2, y / 2, z / 2) ; = max(x, y, z) / 2 LDA K ; Set SC+1 = (K + K+1 + K+2 - SC) / 4 CLC ; = (x/2 + y/2 + z/2 - max(x, y, z) / 2) / 4 ADC K+1 ; = (x + y + z - max(x, y, z)) / 8 ADC K+2 ; SEC ; There is a risk that the addition will overflow here, SBC SC ; but presumably this isn't an issue LSR A LSR A STA SC+1 LSR A ; Set A = (SC+1 / 4) + SC+1 + SC LSR A ; = 5/4 * SC+1 + SC ADC SC+1 ; = 5 * (x + y + z - max(x, y, z)) / (8 * 4) ADC SC ; + max(x, y, z) / 2 ; ; If h is the longest of x, y, z, and a and b are the ; other two sides, then we have: ; ; max(x, y, z) = h ; ; x + y + z - max(x, y, z) = a + b + h - h ; = a + b ; ; So: ; ; A = 5 * (a + b) / (8 * 4) + h / 2 ; = 5/32 * a + 5/32 * b + 1/2 * h ; ; This estimates half the length of the (x, y, z) ; vector, i.e. 0.5 * |x y z|, using an approximation ; that estimates the length within 8% of the correct ; value, and without having to do any multiplication ; or take any square roots CMP T ; If A < T, C will be clear, otherwise C will be set ; ; So the C flag is clear if |x y z| < argument A ; set if |x y z| >= argument A RTS ; Return from the subroutine .farr3 SEC ; Set the C flag to indicate A < x and A < y and A < z RTS ; Return from the subroutine
Name: MU5 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Set K(3 2 1 0) = (A A A A) and clear the C flag
Context: See this subroutine on its own page References: This subroutine is called as follows: * MULT3 calls MU5

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Calculate: (S R) = Q * A
.MULT12 JSR MULT1 ; Set (A P) = Q * A STA S ; Set (S P) = (A P) ; = Q * A SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA P ; Set (S R) = (S P) STA R ; = Q * A RTS ; Return from the subroutine
Name: TAS3 [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the dot product of XX15 and an orientation vector
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOCKIT calls TAS3 * TACTICS (Part 3 of 7) calls TAS3 * TACTICS (Part 7 of 7) calls TAS3

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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