Skip to navigation


Bank 1 (Part 3 of 3)

[NES version]

Name: EDGES [Show more] Type: Subroutine Category: Drawing lines Summary: Draw a horizontal line given a centre and a half-width
Context: See this subroutine on its own page References: This subroutine is called as follows: * SUN (Part 2 of 2) calls EDGES * SUN (Part 2 of 2) calls via EDGES-2

Set X1 and X2 to the x-coordinates of the ends of the horizontal line with centre x-coordinate YY(1 0), and length A in either direction from the centre (so a total line length of 2 * A). In other words, this line: X1 YY(1 0) X2 +-----------------+-----------------+ <- A -> <- A -> The resulting line gets clipped to the edges of the screen, if needed. If the calculation doesn't overflow, we return with the C flag clear, otherwise the C flag gets set to indicate failure.
Arguments: A The half-length of the line YY(1 0) The centre x-coordinate
Returns: C flag Clear if the line fits on-screen, set if it doesn't X1, X2 The x-coordinates of the clipped line Y Y is preserved
Other entry points: EDGES-2 Return the C flag set if argument A is 0
.ED3 BPL ED1 ; We jump here with the status flags set to the result ; of the high byte of this subtraction, and only if the ; high byte is non-zero: ; ; (A X1) = YY(1 0) - argument A ; ; If the result of the subtraction is positive and ; non-zero then the coordinate is not on-screen, so jump ; to ED1 to return the C flag set LDA #0 ; The result of the subtraction is negative, so we have STA X1 ; have gone past the left edge of the screen, so we clip ; the x-coordinate in X1 to 0 CLC ; Clear the C flag to indicate that the clipped line ; fits on-screen RTS ; Return from the subroutine .ED1 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 SEC ; Set the C flag to indicate that the line does not fit ; on-screen RTS ; Return from the subroutine BEQ ED1 ; If we call the routine at EDGES-2, this checks whether ; the argument in A is zero, and if it is, it jumps to ; ED1 to return the C flag set .EDGES STA T ; Set T to the line's half-length in argument A CLC ; We now calculate: ADC YY ; STA X2 ; (A X2) = YY(1 0) + A ; ; to set X2 to the x-coordinate of the right end of the ; line, starting with the low bytes LDA YY+1 ; And then adding the high bytes ADC #0 BMI ED1 ; If the addition is negative then the calculation has ; overflowed, so jump to ED1 to return a failure BEQ P%+6 ; If the high byte A from the result is 0, skip the ; next two instructions, as the result already fits on ; the screen LDA #253 ; The high byte is positive and non-zero, so we went STA X2 ; past the right edge of the screen, so clip X2 to the ; x-coordinate of the right edge of the screen LDA YY ; We now calculate: SEC ; SBC T ; (A X1) = YY(1 0) - argument A STA X1 ; ; to set X1 to the x-coordinate of the left end of the ; line, starting with the low bytes LDA YY+1 ; And then subtracting the high bytes SBC #0 BNE ED3 ; If the high byte of the subtraction is non-zero, then ; jump to ED3 to return a failure if the subtraction has ; taken us off the left edge of the screen LDA X1 ; Set the C flag if X1 >= X2, clear it if X1 < X2 CMP X2 ; ; So this sets the C flag if the line doesn't fit on ; the screen RTS ; Return from the subroutine
Name: DrawSunEdgeLeft [Show more] Type: Subroutine Category: Drawing suns Summary: Draw a sun line in the tile on the left end of a sun row
Context: See this subroutine on its own page References: This subroutine is called as follows: * SUN (Part 2 of 2) calls DrawSunEdgeLeft * DrawSunEdgeRight calls via RTS7 * DrawSunEdgeRight calls via DrawSunEdge

Arguments: A The half-width of the sun line Y The number of the pixel row of the sun line within the tile row (0-7) P The pixel x-coordinate of the start of the middle section of the sun line (i.e. the x-coordinate just to the right of the leftmost tile) YY(1 0) The centre x-coordinate of the sun
Other entry points: RTS7 Contains an RTS DrawSunEdge Draw a sun line from (X1, Y) to (X2, Y)
.DrawSunEdgeLeft LDX P ; Set X2 to P, which contains the x-coordinate just to STX X2 ; the right of the leftmost tile ; ; We can use this as the x-coordinate of the right end ; of the line that we want to draw in the leftmost tile EOR #$FF ; Use two's complement to set X1 = YY(1 0) - A SEC ; ADC YY ; So X1 contains the x-coordinate of the left end of the STA X1 ; sun line LDA YY+1 ADC #$FF BEQ DrawSunEdge ; If the high byte of the result is zero, then the left ; end of the line is on-screen, so jump to DrawSunEdge ; to draw the sun line from (X1, Y) to (X2, Y) BMI sunl1 ; If the high byte of the result is negative, then the ; left end of the line is off the left edge of the ; screen, so jump to sunl1 to draw a clipped sun line ; from (0, Y) to (X2, Y) ; Otherwise the line is off-screen, so return from the ; subroutine without drawing anything .RTS7 RTS ; Return from the subroutine .DrawSunEdge LDA X1 ; If X1 >= X2 then the left end of the line is to the CMP X2 ; right of the right end of the line, so these are not BCS RTS7 ; valid line coordinates and we jump to RTS7 to return ; from the subroutine without drawing anything JMP HLOIN ; Otherwise draw the sun line from (X1, Y) to (X2, Y) ; and return from the subroutine using a tail call .sunl1 ; If we get here then we need to clip the left end of ; the line to fit on-screen LDA #0 ; Draw a clipped the sun line from (0, Y) to (X2, Y) STA X1 ; and return from the subroutine using a tail call JMP HLOIN
Name: DrawSunEdgeRight [Show more] Type: Subroutine Category: Drawing suns Summary: Draw a sun line in the tile on the right end of a sun row
Context: See this subroutine on its own page References: This subroutine is called as follows: * SUN (Part 2 of 2) calls DrawSunEdgeRight

Arguments: A The half-width of the sun line Y The number of the pixel row of the sun line within the tile row (0-7) X1 The pixel x-coordinate of the rightmost tile on the sun line YY(1 0) The centre x-coordinate of the sun
.DrawSunEdgeRight CLC ; Set X1 = YY(1 0) + A ADC YY ; STA X2 ; So X2 contains the x-coordinate of the right end of LDA YY+1 ; the sun line ADC #0 ; X1 is already set to the x-coordinate of the rightmost ; tile, so the line we need to draw is from (X1, Y) to ; (X2, Y) BEQ DrawSunEdge ; If the high byte of the result is zero, then the right ; end of the line is on-screen, so jump to DrawSunEdge ; to draw the sun line from (X1, Y) to (X2, Y) BMI RTS7 ; If the high byte of the result is negative, then the ; right end of the line is off the left edge of the ; screen, so the line is not on-screen and we jump to ; RTS7 to return from the subroutine (as RTS7 contains ; an RTS) ; If we get here then the right end of the line is past ; the right edge of the screen, so we need to clip the ; right end of the line to fit on-screen LDA #253 ; Set X2 = 253 so the line is clipped to the right edge STA X2 ; of the screen CMP X1 ; If X2 <= X1 then the right end of the line is to the BEQ RTS7 ; left of the left end of the line, so these are not BCC RTS7 ; valid line coordinates and we jump to RTS7 to return ; from the subroutine without drawing anything JMP HLOIN ; Otherwise draw the sun line from (X1, Y) to (X2, Y) ; and return from the subroutine using a tail call
Name: CHKON [Show more] Type: Subroutine Category: Drawing circles Summary: Check whether any part of a circle appears on the extended screen
Context: See this subroutine on its own page References: This subroutine is called as follows: * CIRCLE calls CHKON * SUN (Part 1 of 2) calls CHKON

Arguments: K The circle's radius K3(1 0) Pixel x-coordinate of the centre of the circle K4(1 0) Pixel y-coordinate of the centre of the circle
Returns: C flag Clear if any part of the circle appears on-screen, set if none of the circle appears on-screen (A X) Minimum y-coordinate of the circle on-screen (i.e. the y-coordinate of the top edge of the circle) P(2 1) Maximum y-coordinate of the circle on-screen (i.e. the y-coordinate of the bottom edge of the circle)
.CHKON LDA K3 ; Set A = K3 + K CLC ADC K LDA K3+1 ; Set A = K3+1 + 0 + any carry from above, so this ADC #0 ; effectively sets A to the high byte of K3(1 0) + K: ; ; (A ?) = K3(1 0) + K ; ; so A is the high byte of the x-coordinate of the right ; edge of the circle BMI PL21 ; If A is negative then the right edge of the circle is ; to the left of the screen, so jump to PL21 to set the ; C flag and return from the subroutine, as the whole ; circle is off-screen to the left LDA K3 ; Set A = K3 - K SEC SBC K LDA K3+1 ; Set A = K3+1 - 0 - any carry from above, so this SBC #0 ; effectively sets A to the high byte of K3(1 0) - K: ; ; (A ?) = K3(1 0) - K ; ; so A is the high byte of the x-coordinate of the left ; edge of the circle BMI PL31 ; If A is negative then the left edge of the circle is ; to the left of the screen, and we already know the ; right edge is either on-screen or off-screen to the ; right, so skip to PL31 to move on to the y-coordinate ; checks, as at least part of the circle is on-screen in ; terms of the x-axis BNE PL21 ; If A is non-zero, then the left edge of the circle is ; to the right of the screen, so jump to PL21 to set the ; C flag and return from the subroutine, as the whole ; circle is off-screen to the right .PL31 LDA K4 ; Set P+1 = K4 + K CLC ADC K STA P+1 LDA K4+1 ; Set A = K4+1 + 0 + any carry from above, so this ADC #0 ; does the following: ; ; (A P+1) = K4(1 0) + K ; ; so A is the high byte of the y-coordinate of the ; bottom edge of the circle BMI PL21 ; If A is negative then the bottom edge of the circle is ; above the top of the screen, so jump to PL21 to set ; the C flag and return from the subroutine, as the ; whole circle is off-screen to the top STA P+2 ; Store the high byte in P+2, so now we have: ; ; P(2 1) = K4(1 0) + K ; ; i.e. the maximum y-coordinate of the circle on-screen ; (which we return) LDA K4 ; Set X = K4 - K SEC SBC K TAX LDA K4+1 ; Set A = K4+1 - 0 - any carry from above, so this SBC #0 ; does the following: ; ; (A X) = K4(1 0) - K ; ; so A is the high byte of the y-coordinate of the top ; edge of the circle BMI PL44 ; If A is negative then the top edge of the circle is ; above the top of the screen, and we already know the ; bottom edge is either on-screen or below the bottom ; of the screen, so skip to PL44 to clear the C flag and ; return from the subroutine using a tail call, as part ; of the circle definitely appears on-screen BNE PL21 ; If A is non-zero, then the top edge of the circle is ; below the bottom of the screen, so jump to PL21 to set ; the C flag and return from the subroutine, as the ; whole circle is off-screen to the bottom CPX Yx2M1 ; If we get here then A is zero, which means the top ; edge of the circle is within the screen boundary, so ; now we need to check whether it is in the space view ; (in which case it is on-screen) or the dashboard (in ; which case the top of the circle is hidden by the ; dashboard, so the circle isn't on-screen). We do this ; by checking the low byte of the result in X against ; Yx2M1, and returning the C flag from this comparison. ; The value in Yx2M1 is the y-coordinate of the bottom ; pixel row of the space view, so this does the ; following: ; ; * The C flag is set if coordinate (A X) is below the ; bottom row of the space view, i.e. the top edge of ; the circle is hidden by the dashboard ; ; * The C flag is clear if coordinate (A X) is above ; the bottom row of the space view, i.e. the top ; edge of the circle is on-screen RTS ; Return from the subroutine
Name: PL21 [Show more] Type: Subroutine Category: Drawing planets Summary: Return from a planet/sun-drawing routine with a failure flag
Context: See this subroutine on its own page References: This subroutine is called as follows: * CHKON calls PL21

Set the C flag and return from the subroutine. This is used to return from a planet- or sun-drawing routine with the C flag indicating an overflow in the calculation.
.PL21 SEC ; Set the C flag to indicate an overflow RTS ; Return from the subroutine
Name: PL44 [Show more] Type: Subroutine Category: Drawing planets Summary: Return from a planet/sun-drawing routine with a success flag
Context: See this subroutine on its own page References: This subroutine is called as follows: * CHKON calls PL44

Clear the C flag and return from the subroutine. This is used to return from a planet- or sun-drawing routine with the C flag indicating an overflow in the calculation.
.PL44 CLC ; Clear the C flag to indicate success RTS ; Return from the subroutine
Name: PLS3 [Show more] Type: Subroutine Category: Drawing planets Summary: Calculate (Y A P) = 222 * roofv_x / z
Context: See this subroutine on its own page References: This subroutine is called as follows: * PL9 (Part 3 of 3) calls PLS3

Calculate the following, with X determining the vector to use: (Y A P) = 222 * roofv_x / z though in reality only (Y A) is used. Although the code below supports a range of values of X, in practice the routine is only called with X = 15, and then again after X has been incremented to 17. So the values calculated by PLS1 use roofv_x first, then roofv_y. The comments below refer to roofv_x, for the first call.
Arguments: X Determines which of the INWK orientation vectors to divide: * X = 15: divides roofv_x * X = 17: divides roofv_y
Returns: X X gets incremented by 2 so it points to the next coordinate in this orientation vector (so consecutive calls to the routine will start with x, then move onto y and then z)
.PLS3 JSR PLS1 ; Call PLS1 to calculate the following: STA P ; ; P = |roofv_x / z| ; K+3 = sign of roofv_x / z ; ; and increment X to point to roofv_y for the next call LDA #222 ; Set Q = 222, the offset to the crater STA Q STX U ; Store the vector index X in U for retrieval after the ; call to MULTU JSR MULTU ; Call MULTU to calculate ; ; (A P) = P * Q ; = 222 * |roofv_x / z| LDX U ; Restore the vector index from U into X LDY K+3 ; If the sign of the result in K+3 is positive, skip to BPL PL12 ; PL12 to return with Y = 0 EOR #$FF ; Otherwise the result should be negative, so negate the CLC ; high byte of the result using two's complement with ADC #1 ; A = ~A + 1 BEQ PL12 ; If A = 0, jump to PL12 to return with (Y A) = 0 LDY #$FF ; Set Y = $FF to be a negative high byte RTS ; Return from the subroutine .PL12 LDY #0 ; Set Y = 0 to be a positive high byte RTS ; Return from the subroutine
Name: PLS4 [Show more] Type: Subroutine Category: Drawing planets Summary: Calculate CNT2 = arctan(P / A) / 4
Context: See this subroutine on its own page References: This subroutine is called as follows: * PL9 (Part 2 of 3) calls PLS4

Calculate the following: CNT2 = arctan(P / A) / 4 and do the following if nosev_z_hi >= 0: CNT2 = CNT2 + 32 which is the equivalent of adding 180 degrees to the result (or PI radians), as there are 64 segments in a full circle. This routine is called with the following arguments when calculating the equator and meridian for planets: * A = roofv_z_hi, P = -nosev_z_hi * A = sidev_z_hi, P = -nosev_z_hi So it calculates the angle between the planet's orientation vectors, in the z-axis.
.PLS4 STA Q ; Set Q = A JSR ARCTAN ; Call ARCTAN to calculate: ; ; A = arctan(P / Q) ; arctan(P / A) ; ; The result in A will be in the range 0 to 128, which ; represents an angle of 0 to 180 degrees (or 0 to PI ; radians) LDX INWK+14 ; If nosev_z_hi is negative, skip the following BMI P%+4 ; instruction to leave the angle in A as a positive ; integer in the range 0 to 128 (so when we calculate ; CNT2 below, it will be in the right half of the ; anti-clockwise arc that we describe when drawing ; circles, i.e. from 6 o'clock, through 3 o'clock and ; on to 12 o'clock) EOR #%10000000 ; If we get here then nosev_z_hi is positive, so flip ; bit 7 of the angle in A, which is the same as adding ; 128 to give a result in the range 129 to 256 (i.e. 129 ; to 0), or 180 to 360 degrees (so when we calculate ; CNT2 below, it will be in the left half of the ; anti-clockwise arc that we describe when drawing ; circles, i.e. from 12 o'clock, through 9 o'clock and ; on to 6 o'clock) LSR A ; Set CNT2 = A / 4 LSR A STA CNT2 RTS ; Return from the subroutine
Name: PLS5 [Show more] Type: Subroutine Category: Drawing planets Summary: Calculate roofv_x / z and roofv_y / z
Context: See this subroutine on its own page References: This subroutine is called as follows: * PL9 (Part 2 of 3) calls PLS5

Calculate the following divisions of a specified value from one of the orientation vectors (in this example, roofv): (XX16+2 K2+2) = roofv_x / z (XX16+3 K2+3) = roofv_y / z
Arguments: X Determines which of the INWK orientation vectors to divide: * X = 15: divides roofv_x and roofv_y * X = 21: divides sidev_x and sidev_y INWK The planet's ship data block
.PLS5 JSR PLS1 ; Call PLS1 to calculate the following: STA K2+2 ; STY XX16+2 ; K+2 = |roofv_x / z| ; XX16+2 = sign of roofv_x / z ; ; i.e. (XX16+2 K2+2) = roofv_x / z ; ; and increment X to point to roofv_y for the next call JSR PLS1 ; Call PLS1 to calculate the following: STA K2+3 ; STY XX16+3 ; K+3 = |roofv_y / z| ; XX16+3 = sign of roofv_y / z ; ; i.e. (XX16+3 K2+3) = roofv_y / z ; ; and increment X to point to roofv_z for the next call RTS ; Return from the subroutine
Name: ARCTAN [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Calculate A = arctan(P / Q) Deep dive: The sine, cosine and arctan tables
Context: See this subroutine on its own page References: This subroutine is called as follows: * PLS4 calls ARCTAN

Calculate the following: A = arctan(P / Q) In other words, this finds the angle in the right-angled triangle where the opposite side to angle A is length P and the adjacent side to angle A has length Q, so: tan(A) = P / Q The result in A is an integer representing the angle in radians. The routine returns values in the range 0 to 128, which covers 0 to 180 degrees (or 0 to PI radians).
.ARCTAN LDA P ; Set T1 = P EOR Q, which will have the sign of P * Q EOR Q STA T1 LDA Q ; If Q = 0, jump to AR2 to return a right angle BEQ AR2 ASL A ; Set Q = |Q| * 2 (this is a quick way of clearing the STA Q ; sign bit, and we don't need to shift right again as we ; only ever use this value in the division with |P| * 2, ; which we set next) LDA P ; Set A = |P| * 2 ASL A CMP Q ; If A >= Q, i.e. |P| > |Q|, jump to AR1 to swap P BCS AR1 ; and Q around, so we can still use the lookup table JSR ARS1 ; Call ARS1 to set the following from the lookup table: ; ; A = arctan(A / Q) ; = arctan(|P / Q|) SEC ; Set the C flag so the SBC instruction in AR3 will be ; correct, should we jump there .AR4 LDX T1 ; If T1 is negative, i.e. P and Q have different signs, BMI AR3 ; jump down to AR3 to return arctan(-|P / Q|) RTS ; Otherwise P and Q have the same sign, so our result is ; correct and we can return from the subroutine .AR1 ; We want to calculate arctan(t) where |t| > 1, so we ; can use the calculation described in the documentation ; for the ACT table, i.e. 64 - arctan(1 / t) LDX Q ; Swap the values in Q and P, using the fact that we STA Q ; called AR1 with A = P STX P ; TXA ; This also sets A = P (which now contains the original ; argument |Q|) JSR ARS1 ; Call ARS1 to set the following from the lookup table: ; ; A = arctan(A / Q) ; = arctan(|Q / P|) ; = arctan(1 / |P / Q|) STA T ; Set T = 64 - T LDA #64 SBC T BCS AR4 ; Jump to AR4 to continue the calculation (this BCS is ; effectively a JMP as the subtraction will never ; underflow, as ARS1 returns values in the range 0-31) .AR2 ; If we get here then Q = 0, so tan(A) = infinity and ; A is a right angle, or 0.25 of a circle. We allocate ; 255 to a full circle, so we should return 63 for a ; right angle LDA #63 ; Set A to 63, to represent a right angle RTS ; Return from the subroutine .AR3 ; A contains arctan(|P / Q|) but P and Q have different ; signs, so we need to return arctan(-|P / Q|), using ; the calculation described in the documentation for the ; ACT table, i.e. 128 - A STA T ; Set A = 128 - A LDA #128 ; SBC T ; The subtraction will work because we did a SEC before ; calling AR3 RTS ; Return from the subroutine .ARS1 ; This routine fetches arctan(A / Q) from the ACT table, ; so A will be set to an integer in the range 0 to 31 ; that represents an angle from 0 to 45 degrees (or 0 to ; PI / 4 radians) JSR LL28 ; Call LL28 to calculate: ; ; R = 256 * A / Q LDA R ; Set X = R / 8 LSR A ; = 32 * A / Q LSR A ; LSR A ; so X has the value t * 32 where t = A / Q, which is TAX ; what we need to look up values in the ACT table LDA ACT,X ; Fetch ACT+X from the ACT table into A, so now: ; ; A = value in ACT + X ; = value in ACT + (32 * A / Q) ; = arctan(A / Q) RTS ; Return from the subroutine
Name: BLINE [Show more] Type: Subroutine Category: Drawing circles Summary: Draw a circle segment and add it to the ball line heap Deep dive: The ball line heap Drawing circles
Context: See this subroutine on its own page References: This subroutine is called as follows: * CIRCLE2 calls BLINE * PLS22 calls BLINE

Draw a single segment of a circle, adding the point to the ball line heap.
Arguments: CNT The number of this segment STP The step size for the circle K6(1 0) The x-coordinate of the new point on the circle, as a screen coordinate (T X) The y-coordinate of the new point on the circle, as an offset from the centre of the circle FLAG Set to $FF for the first call, so it sets up the first point in the heap but waits until the second call before drawing anything (as we need two points, i.e. two calls, before we can draw a line) K4(1 0) Pixel y-coordinate of the centre of the circle K5(1 0) Screen x-coordinate of the previous point added to the ball line heap (if this is not the first point) K5(3 2) Screen y-coordinate of the previous point added to the ball line heap (if this is not the first point)
Returns: CNT CNT is updated to CNT + STP A The new value of CNT K5(1 0) Screen x-coordinate of the point that we just added to the ball line heap K5(3 2) Screen y-coordinate of the point that we just added to the ball line heap FLAG Set to 0
.BLINE 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 ; Set K6(3 2) = (T X) + K4(1 0) ADC K4 ; = y-coord of centre + y-coord of new point STA K6+2 ; LDA K4+1 ; so K6(3 2) now contains the y-coordinate of the new ADC T ; point on the circle but as a screen coordinate, to go STA K6+3 ; along with the screen x-coordinate in K6(1 0) LDA FLAG ; If FLAG = 0, jump down to BL1 BEQ BL1 INC FLAG ; Flag is $FF so this is the first call to BLINE, so ; increment FLAG to set it to 0, as then the next time ; we call BLINE it can draw the first line, from this ; point to the next JMP BL5 ; This is the first call to BLINE, so we don't need to ; copy the previous point to XX15 as there isn't one, ; so we jump to BL5 to tidy up and return from the ; subroutine .BL1 LDA K5 ; Set XX15 = K5 = x_lo of previous point STA XX15 LDA K5+1 ; Set XX15+1 = K5+1 = x_hi of previous point STA XX15+1 LDA K5+2 ; Set XX15+2 = K5+2 = y_lo of previous point STA XX15+2 LDA K5+3 ; Set XX15+3 = K5+3 = y_hi of previous point STA XX15+3 LDA K6 ; Set XX15+4 = x_lo of new point STA XX15+4 LDA K6+1 ; Set XX15+5 = x_hi of new point STA XX15+5 LDA K6+2 ; Set XX12 = y_lo of new point STA XX12 LDA K6+3 ; Set XX12+1 = y_hi of new point STA XX12+1 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 JSR CLIP ; Call CLIP to see if the new line segment needs to be ; clipped to fit on-screen, returning the clipped line's ; end-points in (X1, Y1) and (X2, Y2) BCS BL5 ; If the C flag is set then the line is not visible on ; screen anyway, so jump to BL5, to avoid drawing and ; storing this line LDA SWAP ; If SWAP = 0, then we didn't have to swap the line BEQ BL9 ; coordinates around during the clipping process, so ; jump to BL9 to skip the following swap LDA X1 ; Otherwise the coordinates were swapped by the call to LDY X2 ; LL145 above, so we swap (X1, Y1) and (X2, Y2) back STA X2 ; again STY X1 LDA Y1 LDY Y2 STA Y2 STY Y1 .BL9 JSR LOIN ; Draw a line from (X1, Y1) to (X2, Y2) .BL5 LDA K6 ; Copy the data for this step point from K6(3 2 1 0) STA K5 ; into K5(3 2 1 0), for use in the next call to BLINE: LDA K6+1 ; STA K5+1 ; * K5(1 0) = screen x-coordinate of this point LDA K6+2 ; STA K5+2 ; * K5(3 2) = screen y-coordinate of this point LDA K6+3 ; STA K5+3 ; They now become the "previous point" in the next call LDA CNT ; Set CNT = CNT + STP CLC ADC STP STA CNT RTS ; Return from the subroutine
Name: STARS [Show more] Type: Subroutine Category: Stardust Summary: The main routine for processing the stardust Deep dive: Sprite usage in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS_b1 calls STARS

Called at the very end of the main flight loop.
.STARS LDX VIEW ; Load the current view into X: ; ; 0 = front ; 1 = rear ; 2 = left ; 3 = right BEQ STARS1 ; If this 0, jump to STARS1 to process the stardust for ; the front view DEX ; If this is view 2 or 3, jump to STARS2 (via ST11) to BNE ST11 ; process the stardust for the left or right views JMP STARS6 ; Otherwise this is the rear view, so jump to STARS6 to ; process the stardust for the rear view .ST11 JMP STARS2 ; Jump to STARS2 for the left or right views, as it's ; too far for the branch instruction above
Name: STARS1 [Show more] Type: Subroutine Category: Stardust Summary: Process the stardust for the front view Deep dive: Stardust in the front view Deep dive: Sprite usage in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS calls STARS1

This moves the stardust towards us according to our speed (so the dust rushes past us), and applies our current pitch and roll to each particle of dust, so the stardust moves correctly when we steer our ship. When a stardust particle rushes past us and falls off the side of the screen, its memory is recycled as a new particle that's positioned randomly on-screen. These are the calculations referred to in the commentary: 1. q = 64 * speed / z_hi 2. z = z - speed * 64 3. y = y + |y_hi| * q 4. x = x + |x_hi| * q 5. y = y + alpha * x / 256 6. x = x - alpha * y / 256 7. x = x + 2 * (beta * y / 256) ^ 2 8. y = y - beta * 256 For more information see the deep dive on "Stardust in the front view".
.STARS1 LDY NOSTM ; Set Y to the current number of stardust particles, so ; we can use it as a counter through all the stardust ; In the following, we're going to refer to the 16-bit ; space coordinates of the current particle of stardust ; (i.e. the Y-th particle) like this: ; ; x = (x_hi x_lo) ; y = (y_hi y_lo) ; z = (z_hi z_lo) ; ; These values are stored in (SX+Y SXL+Y), (SY+Y SYL+Y) ; and (SZ+Y SZL+Y) respectively .STL1 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 JSR DV42 ; Call DV42 to set the following: ; ; (P R) = 256 * DELTA / z_hi ; = 256 * speed / z_hi ; ; The maximum value returned is P = 2 and R = 128 (see ; DV42 for an explanation) LDA R ; Set A = R, so now: ; ; (P A) = 256 * speed / z_hi LSR P ; Rotate (P A) right by 2 places, which sets P = 0 (as P ROR A ; has a maximum value of 2) and leaves: LSR P ; ROR A ; A = 64 * speed / z_hi ORA #1 ; Make sure A is at least 1, and store it in Q, so we STA Q ; now have result 1 above: ; ; Q = 64 * speed / z_hi LDA SZL,Y ; We now calculate the following: SBC DELT4 ; STA SZL,Y ; (z_hi z_lo) = (z_hi z_lo) - DELT4(1 0) ; ; starting with the low bytes LDA SZ,Y ; And then we do the high bytes STA ZZ ; SBC DELT4+1 ; We also set ZZ to the original value of z_hi, which we STA SZ,Y ; use below to remove the existing particle ; ; So now we have result 2 above: ; ; z = z - DELT4(1 0) ; = z - speed * 64 JSR MLU1 ; Call MLU1 to set: ; ; Y1 = y_hi ; ; (A P) = |y_hi| * Q ; ; So Y1 contains the original value of y_hi, which we ; use below to remove the existing particle ; We now calculate: ; ; (S R) = YY(1 0) = (A P) + y STA YY+1 ; First we do the low bytes with: LDA P ; ADC SYL,Y ; YY+1 = A STA YY ; R = YY = P + y_lo STA R ; ; so we get this: ; ; (? R) = YY(1 0) = (A P) + y_lo LDA Y1 ; And then we do the high bytes with: ADC YY+1 ; STA YY+1 ; S = YY+1 = y_hi + YY+1 STA S ; ; so we get our result: ; ; (S R) = YY(1 0) = (A P) + (y_hi y_lo) ; = |y_hi| * Q + y ; ; which is result 3 above, and (S R) is set to the new ; value of y LDA SX,Y ; Set X1 = A = x_hi STA X1 ; ; So X1 contains the original value of x_hi, which we ; use below to remove the existing particle JSR MLU2 ; Set (A P) = |x_hi| * Q ; We now calculate: ; ; XX(1 0) = (A P) + x STA XX+1 ; First we store the high byte A in XX+1 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 ; Then we do the low bytes: ADC SXL,Y ; STA XX ; XX(1 0) = (A P) + x_lo LDA X1 ; And then we do the high bytes: ADC XX+1 ; STA XX+1 ; XX(1 0) = XX(1 0) + (x_hi 0) ; ; so we get our result: ; ; XX(1 0) = (A P) + x ; = |x_hi| * Q + x ; ; which is result 4 above, and we also have: ; ; A = XX+1 = (|x_hi| * Q + x) / 256 ; ; i.e. A is the new value of x, divided by 256 EOR ALP2+1 ; EOR with the flipped sign of the roll angle alpha, so ; A has the opposite sign to the flipped roll angle ; alpha, i.e. it gets the same sign as alpha JSR MLS1 ; Call MLS1 to calculate: ; ; (A P) = A * ALP1 ; = (x / 256) * alpha JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = (x / 256) * alpha + y ; = y + alpha * x / 256 STA YY+1 ; Set YY(1 0) = (A X) to give: STX YY ; ; YY(1 0) = y + alpha * x / 256 ; ; which is result 5 above, and we also have: ; ; A = YY+1 = y + alpha * x / 256 ; ; i.e. A is the new value of y, divided by 256 EOR ALP2 ; EOR A with the correct sign of the roll angle alpha, ; so A has the opposite sign to the roll angle alpha JSR MLS2 ; Call MLS2 to calculate: ; ; (S R) = XX(1 0) ; = x ; ; (A P) = A * ALP1 ; = -y / 256 * alpha JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = -y / 256 * alpha + x STA XX+1 ; Set XX(1 0) = (A X), which gives us result 6 above: STX XX ; ; x = x - alpha * y / 256 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 BET1 ; Fetch the pitch magnitude into X LDA YY+1 ; Set A to y_hi and set it to the flipped sign of beta EOR BET2+1 JSR MULTS-2 ; Call MULTS-2 to calculate: ; ; (A P) = X * A ; = -beta * y_hi STA Q ; Store the high byte of the result in Q, so: ; ; Q = -beta * y_hi / 256 JSR MUT2 ; Call MUT2 to calculate: ; ; (S R) = XX(1 0) = x ; ; (A P) = Q * A ; = (-beta * y_hi / 256) * (-beta * y_hi / 256) ; = (beta * y / 256) ^ 2 ASL P ; Double (A P), store the top byte in A and set the C ROL A ; flag to bit 7 of the original A, so this does: STA T ; ; (T P) = (A P) << 1 ; = 2 * (beta * y / 256) ^ 2 LDA #0 ; Set bit 7 in A to the sign bit from the A in the ROR A ; calculation above and apply it to T, so we now have: ORA T ; ; (A P) = (A P) * 2 ; = 2 * (beta * y / 256) ^ 2 ; ; with the doubling retaining the sign of (A P) JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = 2 * (beta * y / 256) ^ 2 + x STA XX+1 ; Store the high byte A in XX+1 TXA ; Store the low byte X in x_lo STA SXL,Y ; So (XX+1 x_lo) now contains: ; ; x = x + 2 * (beta * y / 256) ^ 2 ; ; which is result 7 above LDA YY ; Set (S R) = YY(1 0) = y STA R LDA YY+1 STA S LDA #0 ; Set P = 0 STA P SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA BETA ; Set A = -beta, so: EOR #%10000000 ; ; (A P) = (-beta 0) ; = -beta * 256 ; Calculate the following: ; ; (YY+1 y_lo) = (A P) + (S R) ; = -beta * 256 + y ; ; i.e. y = y - beta * 256, which is result 8 above JSR ADD ; Set (A X) = (A P) + (S R) STA YY+1 ; Set YY+1 to A, the high byte of the result TXA ; Set SYL+Y to X, the low byte of the result STA SYL,Y ; We now have our newly moved stardust particle at ; x-coordinate (XX+1 x_lo) and y-coordinate (YY+1 y_lo) ; and distance z_hi, so we draw it if it's still on ; screen, otherwise we recycle it as a new bit of ; stardust and draw that LDA XX+1 ; Set X1 and x_hi to the high byte of XX in XX+1, so STA X1 ; the new x-coordinate is in (x_hi x_lo) and the high STA SX,Y ; byte is in X1 AND #%01111111 ; If |x_hi| >= 120 then jump to KILL1 to recycle this CMP #120 ; particle, as it's gone off the side of the screen, BCS KILL1 ; and rejoin at STC1 with the new particle LDA YY+1 ; Set Y1 and y_hi to the high byte of YY in YY+1, so STA SY,Y ; the new x-coordinate is in (y_hi y_lo) and the high STA Y1 ; byte is in Y1 AND #%01111111 ; If |y_hi| >= 120 then jump to KILL1 to recycle this CMP #120 ; particle, as it's gone off the top or bottom of the BCS KILL1 ; screen, and rejoin at STC1 with the new particle LDA SZ,Y ; If z_hi < 16 then jump to KILL1 to recycle this CMP #16 ; particle, as it's so close that it's effectively gone BCC KILL1 ; past us, and rejoin at STC1 with the new particle STA ZZ ; Set ZZ to the z-coordinate in z_hi .STC1 JSR PIXEL2 ; Draw a stardust particle at (X1,Y1) with distance ZZ, ; i.e. draw the newly moved particle at (x_hi, y_hi) ; with distance z_hi DEY ; Decrement the loop counter to point to the next ; stardust particle BEQ P%+5 ; If we have just done the last particle, skip the next ; instruction to return from the subroutine JMP STL1 ; We have more stardust to process, so jump back up to ; STL1 for the next particle RTS ; Return from the subroutine .KILL1 ; Our particle of stardust just flew past us, so let's ; recycle that particle, starting it at a random ; position that isn't too close to the centre point JSR DORND ; Set A and X to random numbers ORA #4 ; Make sure A is at least 4 and store it in Y1 and y_hi, STA Y1 ; so the new particle starts at least 4 pixels above or STA SY,Y ; below the centre of the screen JSR DORND ; Set A and X to random numbers ORA #16 ; Make sure A is at least 16 and is a multiple of 16 and AND #%11110000 ; store it in X1 and x_hi, so the new particle starts at STA X1 ; least 16 pixels either side of the centre of the STA SX,Y ; screen and on a spaced-out grid that's 16 pixels wide JSR DORND ; Set A and X to random numbers ORA #144 ; Make sure A is at least 144 and store it in ZZ and STA SZ,Y ; z_hi so the new particle starts in the far distance STA ZZ 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 Y1 ; Set A to the new value of y_hi. This has no effect as ; STC1 starts with a jump to PIXEL2, which starts with a ; LDA instruction JMP STC1 ; Jump up to STC1 to draw this new particle
Name: STARS6 [Show more] Type: Subroutine Category: Stardust Summary: Process the stardust for the rear view Deep dive: Sprite usage in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS calls STARS6

This routine is very similar to STARS1, which processes stardust for the front view. The main difference is that the direction of travel is reversed, so the signs in the calculations are different, as well as the order of the first batch of calculations. When a stardust particle falls away into the far distance, it is removed from the screen and its memory is recycled as a new particle, positioned randomly along one of the four edges of the screen. These are the calculations referred to in the commentary: 1. q = 64 * speed / z_hi 2. z = z - speed * 64 3. y = y + |y_hi| * q 4. x = x + |x_hi| * q 5. y = y + alpha * x / 256 6. x = x - alpha * y / 256 7. x = x + 2 * (beta * y / 256) ^ 2 8. y = y - beta * 256 For more information see the deep dive on "Stardust in the front view".
.STARS6 LDY NOSTM ; Set Y to the current number of stardust particles, so ; we can use it as a counter through all the stardust .STL6 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 JSR DV42 ; Call DV42 to set the following: ; ; (P R) = 256 * DELTA / z_hi ; = 256 * speed / z_hi ; ; The maximum value returned is P = 2 and R = 128 (see ; DV42 for an explanation) LDA R ; Set A = R, so now: ; ; (P A) = 256 * speed / z_hi LSR P ; Rotate (P A) right by 2 places, which sets P = 0 (as P ROR A ; has a maximum value of 2) and leaves: LSR P ; ROR A ; A = 64 * speed / z_hi ORA #1 ; Make sure A is at least 1, and store it in Q, so we STA Q ; now have result 1 above: ; ; Q = 64 * speed / z_hi LDA SX,Y ; Set X1 = A = x_hi STA X1 ; ; So X1 contains the original value of x_hi, which we ; use below to remove the existing particle JSR MLU2 ; Set (A P) = |x_hi| * Q ; We now calculate: ; ; XX(1 0) = x - (A P) STA XX+1 ; First we do the low bytes: LDA SXL,Y ; SBC P ; XX(1 0) = x_lo - (A P) STA XX LDA X1 ; And then we do the high bytes: SBC XX+1 ; STA XX+1 ; XX(1 0) = (x_hi 0) - XX(1 0) ; ; so we get our result: ; ; XX(1 0) = x - (A P) ; = x - |x_hi| * Q ; ; which is result 2 above, and we also have: JSR MLU1 ; Call MLU1 to set: ; ; Y1 = y_hi ; ; (A P) = |y_hi| * Q ; ; So Y1 contains the original value of y_hi, which we ; use below to remove the existing particle ; We now calculate: ; ; (S R) = YY(1 0) = y - (A P) STA YY+1 ; First we store the high byte A in YY+1 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 SYL,Y ; Then we do the low bytes with: SBC P ; STA YY ; YY+1 = A STA R ; R = YY = y_lo - P ; ; so we get this: ; ; (? R) = YY(1 0) = y_lo - (A P) LDA Y1 ; And then we do the high bytes with: SBC YY+1 ; STA YY+1 ; S = YY+1 = y_hi - YY+1 STA S ; ; so we get our result: ; ; (S R) = YY(1 0) = (y_hi y_lo) - (A P) ; = y - |y_hi| * Q ; ; which is result 3 above, and (S R) is set to the new ; value of y LDA SZL,Y ; We now calculate the following: ADC DELT4 ; STA SZL,Y ; (z_hi z_lo) = (z_hi z_lo) + DELT4(1 0) ; ; starting with the low bytes LDA SZ,Y ; And then we do the high bytes STA ZZ ; ADC DELT4+1 ; We also set ZZ to the original value of z_hi, which we STA SZ,Y ; use below to remove the existing particle ; ; So now we have result 4 above: ; ; z = z + DELT4(1 0) ; = z + speed * 64 LDA XX+1 ; EOR x with the correct sign of the roll angle alpha, EOR ALP2 ; so A has the opposite sign to the roll angle alpha JSR MLS1 ; Call MLS1 to calculate: ; ; (A P) = A * ALP1 ; = (-x / 256) * alpha JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = (-x / 256) * alpha + y ; = y - alpha * x / 256 STA YY+1 ; Set YY(1 0) = (A X) to give: STX YY ; ; YY(1 0) = y - alpha * x / 256 ; ; which is result 5 above, and we also have: ; ; A = YY+1 = y - alpha * x / 256 ; ; i.e. A is the new value of y, divided by 256 EOR ALP2+1 ; EOR with the flipped sign of the roll angle alpha, so ; A has the opposite sign to the flipped roll angle ; alpha, i.e. it gets the same sign as alpha JSR MLS2 ; Call MLS2 to calculate: ; ; (S R) = XX(1 0) ; = x ; ; (A P) = A * ALP1 ; = y / 256 * alpha JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = y / 256 * alpha + x STA XX+1 ; Set XX(1 0) = (A X), which gives us result 6 above: STX XX ; ; x = x + alpha * y / 256 LDA YY+1 ; Set A to y_hi and set it to the flipped sign of beta EOR BET2+1 LDX BET1 ; Fetch the pitch magnitude into X JSR MULTS-2 ; Call MULTS-2 to calculate: ; ; (A P) = X * A ; = beta * y_hi STA Q ; Store the high byte of the result in Q, so: ; ; Q = beta * y_hi / 256 LDA XX+1 ; Set S = x_hi STA S EOR #%10000000 ; Flip the sign of A, so A now contains -x JSR MUT1 ; Call MUT1 to calculate: ; ; R = XX = x_lo ; ; (A P) = Q * A ; = (beta * y_hi / 256) * (-beta * y_hi / 256) ; = (-beta * y / 256) ^ 2 ASL P ; Double (A P), store the top byte in A and set the C ROL A ; flag to bit 7 of the original A, so this does: STA T ; ; (T P) = (A P) << 1 ; = 2 * (-beta * y / 256) ^ 2 LDA #0 ; Set bit 7 in A to the sign bit from the A in the ROR A ; calculation above and apply it to T, so we now have: ORA T ; ; (A P) = -2 * (beta * y / 256) ^ 2 ; ; with the doubling retaining the sign of (A P) JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = -2 * (beta * y / 256) ^ 2 + x STA XX+1 ; Store the high byte A in XX+1 TXA ; Store the low byte X in x_lo STA SXL,Y ; So (XX+1 x_lo) now contains: ; ; x = x - 2 * (beta * y / 256) ^ 2 ; ; which is result 7 above LDA YY ; Set (S R) = YY(1 0) = y (low byte) STA R SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 LDA YY+1 ; Set (S R) = YY(1 0) = y (high byte) STA S LDA #0 ; Set P = 0 STA P LDA BETA ; Set A = beta, so (A P) = (beta 0) = beta * 256 ; Calculate the following: ; ; (YY+1 y_lo) = (A P) + (S R) ; = -beta * 256 + y ; ; i.e. y = y - beta * 256, which is result 8 above JSR ADD ; Set (A X) = (A P) + (S R) STA YY+1 ; Set YY+1 to A, the high byte of the result TXA ; Set SYL+Y to X, the low byte of the result STA SYL,Y ; We now have our newly moved stardust particle at ; x-coordinate (XX+1 x_lo) and y-coordinate (YY+1 y_lo) ; and distance z_hi, so we draw it if it's still on ; screen, otherwise we recycle it as a new bit of ; stardust and draw that LDA XX+1 ; Set X1 and x_hi to the high byte of XX in XX+1, so STA X1 ; the new x-coordinate is in (x_hi x_lo) and the high STA SX,Y ; byte is in X1 LDA YY+1 ; Set Y1 and y_hi to the high byte of YY in YY+1, so STA SY,Y ; the new x-coordinate is in (y_hi y_lo) and the high STA Y1 ; byte is in Y1 AND #%01111111 ; If |y_hi| >= 110 then jump to KILL6 to recycle this CMP #110 ; particle, as it's gone off the top or bottom of the BCS KILL6 ; screen, and rejoin at STC6 with the new particle LDA SZ,Y ; If z_hi >= 160 then jump to star1 to recycle this CMP #160 ; particle, as it's so far away that it's too far to BCS star1 ; see, and rejoin at STC1 with the new particle STA ZZ ; Set ZZ to the z-coordinate in z_hi .STC6 JSR PIXEL2 ; Draw a stardust particle at (X1,Y1) with distance ZZ, ; i.e. draw the newly moved particle at (x_hi, y_hi) ; with distance z_hi DEY ; Decrement the loop counter to point to the next ; stardust particle BEQ ST3 ; If we have just done the last particle, skip the next ; instruction to return from the subroutine JMP STL6 ; We have more stardust to process, so jump back up to ; STL6 for the next particle .ST3 RTS ; Return from the subroutine .KILL6 JSR DORND ; Set A and X to random numbers AND #31 ; Clear the sign bit of A and set it to a random number ; in the range 0 to 31 ADC #10 ; Make sure A is at least 10 and store it in z_hi and STA SZ,Y ; ZZ, so the new particle starts close to us STA ZZ LSR A ; Divide A by 2 and randomly set the C flag BCS ST4 ; Jump to ST4 half the time LSR A ; Randomly set the C flag again LDA #224 ; Set A to either +112 or -112 (224 >> 1) depending on ROR A ; the C flag, as this is a sign-magnitude number with ; the C flag rotated into its sign bit STA X1 ; Set x_hi and X1 to A, so this particle starts on STA SX,Y ; either the left or right edge of the screen JSR DORND ; Set A and X to random numbers AND #%10111111 ; Clear bit 6 of A so A is in the range -63 to +63 STA Y1 ; Set y_hi and Y1 to random numbers, so the particle STA SY,Y ; starts anywhere along either the left or right edge JMP STC6 ; Jump up to STC6 to draw this new particle .star1 JSR DORND ; Set A and X to random numbers AND #%01111111 ; Clear the sign bit of A to get |A| ADC #10 ; Make sure A is at least 10 and store it in z_hi and STA SZ,Y ; ZZ, so the new particle starts close to us STA ZZ .ST4 JSR DORND ; Set A and X to random numbers AND #%11111001 ; Clear bits 1 and 2 of A so A is a random multiple of 8 ; in the range -120 to +120, randomly minus or plus 1 STA X1 ; Set x_hi and X1 to random numbers, so the particle STA SX,Y ; starts anywhere along the x-axis LSR A ; Randomly set the C flag LDA #216 ; Set A to either +108 or -108 (216 >> 1) depending on ROR A ; the C flag, as this is a sign-magnitude number with ; the C flag rotated into its sign bit STA Y1 ; Set y_hi and Y1 to A, so the particle starts anywhere STA SY,Y ; along either the top or bottom edge of the screen BNE STC6 ; Jump up to STC6 to draw this new particle (this BNE is ; effectively a JMP as A will never be zero)
Name: STARS2 [Show more] Type: Subroutine Category: Stardust Summary: Process the stardust for the left or right view Deep dive: Stardust in the side views Sprite usage in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS calls STARS2

This moves the stardust sideways according to our speed and which side we are looking out of, and applies our current pitch and roll to each particle of dust, so the stardust moves correctly when we steer our ship. These are the calculations referred to in the commentary: 1. delta_x = 8 * 256 * speed / z_hi 2. x = x + delta_x 3. x = x + beta * y 4. y = y - beta * x 5. x = x - alpha * x * y 6. y = y + alpha * y * y + alpha For more information see the deep dive on "Stardust in the side views".
Arguments: X The view to process: * X = 1 for left view * X = 2 for right view
.STARS2 LDA #0 ; Set A to 0 so we can use it to capture a sign bit CPX #2 ; If X >= 2 then the C flag is set ROR A ; Roll the C flag into the sign bit of A and store in STA RAT ; RAT, so: ; ; * Left view, C is clear so RAT = 0 (positive) ; ; * Right view, C is set so RAT = 128 (negative) ; ; RAT represents the end of the x-axis where we want new ; stardust particles to come from: positive for the left ; view where new particles come in from the right, ; negative for the right view where new particles come ; in from the left EOR #%10000000 ; Set RAT2 to the opposite sign, so: STA RAT2 ; ; * Left view, RAT2 = 128 (negative) ; ; * Right view, RAT2 = 0 (positive) ; ; RAT2 represents the direction in which stardust ; particles should move along the x-axis: negative for ; the left view where particles go from right to left, ; positive for the right view where particles go from ; left to right JSR ST2 ; Call ST2 to flip the signs of the following if this is ; the right view: ALPHA, ALP2, ALP2+1, BET2 and BET2+1 LDY NOSTM ; Set Y to the current number of stardust particles, so ; we can use it as a counter through all the stardust .STL2 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 SZ,Y ; Set A = ZZ = z_hi STA ZZ ; We also set ZZ to the original value of z_hi, which we ; use below to remove the existing particle LSR A ; Set A = z_hi / 8 LSR A LSR A JSR DV41 ; Call DV41 to set the following: ; ; (P R) = 256 * DELTA / A ; = 256 * speed / (z_hi / 8) ; = 8 * 256 * speed / z_hi ; ; This represents the distance we should move this ; particle along the x-axis, let's call it delta_x LDA P ; Store the high byte of delta_x in newzp STA newzp EOR RAT2 ; Set S = P but with the sign from RAT2, so we now have STA S ; the distance delta_x with the correct sign in (S R): ; ; (S R) = delta_x ; = 8 * 256 * speed / z_hi ; ; So (S R) is the delta, signed to match the direction ; the stardust should move in, which is result 1 above LDA SXL,Y ; Set (A P) = (x_hi x_lo) STA P ; = x LDA SX,Y STA X1 ; Set X1 = A, so X1 contains the original value of x_hi, ; which we use below to remove the existing particle JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = x + delta_x STA S ; Set (S R) = (A X) STX R ; = x + delta_x 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 SY,Y ; Set A = y_hi STA Y1 ; Set Y1 = A, so Y1 contains the original value of y_hi, ; which we use below to remove the existing particle EOR BET2 ; Give A the correct sign of A * beta, i.e. y_hi * beta LDX BET1 ; Fetch |beta| from BET1, the pitch angle JSR MULTS-2 ; Call MULTS-2 to calculate: ; ; (A P) = X * A ; = beta * y_hi JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = beta * y + x + delta_x STX XX ; Set XX(1 0) = (A X), which gives us results 2 and 3 STA XX+1 ; above, done at the same time: ; ; x = x + delta_x + beta * y LDX SYL,Y ; Set (S R) = (y_hi y_lo) STX R ; = y LDX Y1 STX S LDX BET1 ; Fetch |beta| from BET1, the pitch angle EOR BET2+1 ; Give A the opposite sign to x * beta JSR MULTS-2 ; Call MULTS-2 to calculate: ; ; (A P) = X * A ; = -beta * x JSR ADD ; Call ADD to calculate: ; ; (A X) = (A P) + (S R) ; = -beta * x + y STX YY ; Set YY(1 0) = (A X), which gives us result 4 above: STA YY+1 ; ; y = y - beta * x LDX ALP1 ; Set X = |alpha| from ALP2, the roll angle EOR ALP2 ; Give A the correct sign of A * alpha, i.e. y_hi * ; alpha JSR MULTS-2 ; Call MULTS-2 to calculate: ; ; (A P) = X * A ; = alpha * y STA Q ; Set Q = high byte of alpha * y LDA XX ; Set (S R) = XX(1 0) STA R ; = x LDA XX+1 ; STA S ; and set A = y_hi at the same time EOR #%10000000 ; Flip the sign of A = -x_hi JSR MAD ; Call MAD to calculate: ; ; (A X) = Q * A + (S R) ; = alpha * y * -x + x STA XX+1 ; Store the high byte A in XX+1 TXA ; Store the low byte X in x_lo STA SXL,Y ; So (XX+1 x_lo) now contains result 5 above: ; ; x = x - alpha * x * y 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 YY ; Set (S R) = YY(1 0) STA R ; = y LDA YY+1 ; STA S ; and set A = y_hi at the same time JSR MAD ; Call MAD to calculate: ; ; (A X) = Q * A + (S R) ; = alpha * y * y_hi + y STA S ; Set (S R) = (A X) STX R ; = y + alpha * y * y LDA #0 ; Set P = 0 STA P LDA ALPHA ; Set A = alpha, so: ; ; (A P) = (alpha 0) ; = alpha / 256 ; Calculate the following: ; ; (YY+1 y_lo) = (A P) + (S R) ; = alpha * 256 + y + alpha * y * y ; ; i.e. y = y + alpha / 256 + alpha * y^2, which is ; result 6 above JSR ADD ; Set (A X) = (A P) + (S R) STA YY+1 ; Set YY+1 to A, the high byte of the result TXA ; Set SYL+Y to X, the low byte of the result STA SYL,Y ; We now have our newly moved stardust particle at ; x-coordinate (XX+1 x_lo) and y-coordinate (YY+1 y_lo) ; and distance z_hi, so we draw it if it's still on ; screen, otherwise we recycle it as a new bit of ; stardust and draw that LDA XX+1 ; Set X1 and x_hi to the high byte of XX in XX+1, so STA SX,Y ; the new x-coordinate is in (x_hi x_lo) and the high STA X1 ; byte is in X1 AND #%01111111 ; Set A = |x_hi| CMP #120 ; If |x_hi| >= 120 then jump to KILL2 to recycle this BCS KILL2 ; particle, as it's gone off the side of the screen, ; and rejoin at STC2 with the new particle EOR #%01111111 ; Set A = ~|x_hi|, which is the same as -(x_hi + 1) ; using two's complement CMP newzp ; If newzp <= -(x_hi + 1), then the particle has been BCC KILL2 ; moved off the side of the screen and has wrapped BEQ KILL2 ; round to the other side, jump to KILL2 to recycle this ; particle and rejoin at STC2 with the new particle ; ; In the original BBC Micro versions, this test simply ; checks whether |x_hi| >= 116, but this version using ; newzp doesn't hard-code the screen width, so this is ; presumably a change that was introduced to support ; the different screen sizes of the other platforms LDA YY+1 ; Set Y1 and y_hi to the high byte of YY in YY+1, so STA SY,Y ; the new x-coordinate is in (y_hi y_lo) and the high STA Y1 ; byte is in Y1 AND #%01111111 ; If |y_hi| >= 116 then jump to ST5 to recycle this CMP #116 ; particle, as it's gone off the top or bottom of the BCS ST5 ; screen, and rejoin at STC2 with the new particle .STC2 JSR PIXEL2 ; Draw a stardust particle at (X1,Y1) with distance ZZ, ; i.e. draw the newly moved particle at (x_hi, y_hi) ; with distance z_hi DEY ; Decrement the loop counter to point to the next ; stardust particle BEQ ST2 ; If we have just done the last particle, skip the next ; instruction to return from the subroutine JMP STL2 ; We have more stardust to process, so jump back up to ; STL2 for the next particle ; Fall through into ST2 to restore the signs of the ; following if this is the right view: ALPHA, ALP2, ; ALP2+1, BET2 and BET2+1 .ST2 LDA ALPHA ; If this is the right view, flip the sign of ALPHA EOR RAT STA ALPHA LDA ALP2 ; If this is the right view, flip the sign of ALP2 EOR RAT STA ALP2 EOR #%10000000 ; If this is the right view, flip the sign of ALP2+1 STA ALP2+1 LDA BET2 ; If this is the right view, flip the sign of BET2 EOR RAT STA BET2 EOR #%10000000 ; If this is the right view, flip the sign of BET2+1 STA BET2+1 RTS ; Return from the subroutine .KILL2 JSR DORND ; Set A and X to random numbers STA Y1 ; Set y_hi and Y1 to random numbers, so the particle STA SY,Y ; starts anywhere along the y-axis LDA #115 ; Make sure A is at least 115 and has the sign in RAT ORA RAT STA X1 ; Set x_hi and X1 to A, so this particle starts on the STA SX,Y ; correct edge of the screen for new particles BNE STF1 ; Jump down to STF1 to set the z-coordinate (this BNE is ; effectively a JMP as A will never be zero) .ST5 JSR DORND ; Set A and X to random numbers STA X1 ; Set x_hi and X1 to random numbers, so the particle STA SX,Y ; starts anywhere along the x-axis LDA #126 ; Make sure A is at least 126 and has the sign in AL2+1, ORA ALP2+1 ; the flipped sign of the roll angle alpha STA Y1 ; Set y_hi and Y1 to A, so the particle starts at the STA SY,Y ; top or bottom edge, depending on the current roll ; angle alpha .STF1 JSR DORND ; Set A and X to random numbers ORA #8 ; Make sure A is at least 8 and store it in z_hi and STA ZZ ; ZZ, so the new particle starts at any distance from STA SZ,Y ; us, but not too close BNE STC2 ; Jump up to STC2 to draw this new particle (this BNE is ; effectively a JMP as A will never be zero)
Name: yHangarFloor [Show more] Type: Variable Category: Ship hangar Summary: Pixel y-coordinates for the four horizontal lines that make up the floor of the ship hangar
Context: See this variable on its own page References: This variable is used as follows: * HANGER uses yHangarFloor
.yHangarFloor EQUB 80 EQUB 88 EQUB 98 EQUB 120
Name: HANGER [Show more] Type: Subroutine Category: Ship hangar Summary: Display the ship hangar
Context: See this subroutine on its own page References: This subroutine is called as follows: * HALL calls HANGER

This routine is called after the ships in the hangar have been drawn, so all it has to do is draw the hangar's background. The hangar background is made up of two parts: * The hangar floor consists of four screen-wide horizontal lines at the y-coordinates given in the yHangarFloor table, which are close together at the horizon and further apart as the eye moves down and towards us, giving the hangar a simple sense of perspective * The back wall of the hangar consists of equally spaced vertical lines that join the horizon to the top of the screen The ships in the hangar have already been drawn by this point, so the lines are drawn so they don't overlap anything that's already there, which makes them look like they are behind and below the ships. This is achieved by drawing the lines in from the screen edges until they bump into something already on-screen. For the horizontal lines, when there are multiple ships in the hangar, this also means drawing lines between the ships, as well as in from each side. 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.
.HANGER ; We start by drawing the floor LDX #0 ; We are going to work our way through the four lines in ; the hangar floor, so .hang1 STX TGT ; Store the line number in TGT so we can retrieve it ; later LDA yHangarFloor,X ; Set Y to the pixel y-coordinate of the line, from the TAY ; yHangarFloor table LDA #8 ; Set A = 8 so the call to HAL3 draws a horizontal line ; that starts at pixel x-coordinate 8 (i.e. just inside ; the left box edge surrounding the view) LDX #28 ; Set X = 28 so the call to HAL3 draws a horizontal line ; of up to 28 blocks (i.e. almost the full screen width) JSR HAL3 ; Call HAL3 to draw a line from the left edge of the ; screen, going right until we bump into something ; already on-screen, at which point it stops drawing LDA #240 ; Set A = 240 so the call to HAS3 draws a horizontal ; line that starts at pixel x-coordinate 240 (i.e. just ; inside the right box edge surrounding the view) LDX #28 ; Set X = 28 so the call to HAS3 draws a horizontal line ; of up to 28 blocks (i.e. almost the full screen width) JSR HAS3 ; Draw a horizontal line from the right edge of the ; screen, going left until we bump into something ; already on-screen, at which point stop drawing LDA HANGFLAG ; Fetch the value of HANGFLAG, which gets set to 0 in ; the HALL routine above if there is only one ship BEQ hang2 ; If HANGFLAG is zero, jump to hang2 to skip the ; following as there is only one ship in the hangar ; If we get here then there are multiple ships in the ; hangar, so we also need to draw the horizontal line in ; the gap between the ships LDA #128 ; Set A = 128 so the call to HAL3 draws a horizontal ; line that starts at pixel x-coordinate 128 (i.e. ; from halfway across the screen) LDX #12 ; Set X = 12 so the call to HAL3 draws a horizontal line ; of up to 12 blocks, which will be enough to draw ; between the ships JSR HAL3 ; Call HAL3 to draw a line from the halfway point across ; the right half of the screen, going right until we ; bump into something already on-screen, at which point ; it stops drawing LDA #127 ; Set A = 127 so the call to HAS3 draws a horizontal ; line that starts at pixel x-coordinate 127 (i.e. ; just before the halfway point) LDX #12 ; Set X = 12 so the call to HAL3 draws a horizontal line ; of up to 12 blocks, which will be enough to draw ; between the ships JSR HAS3 ; Draw a horizontal line from the right edge of the ; screen, going left until we bump into something ; already on-screen, at which point stop drawing .hang2 ; We have finished threading our horizontal line behind ; the ships already on-screen, so now for the next line LDX TGT ; Set X to the number of the floor line we are drawing INX ; Increment X to move on to the next floor line CPX #4 ; Loop back to hang1 to draw the next floor line until BNE hang1 ; we have drawn all four ; The floor is done, so now we move on to the back wall JSR DORND ; Set A to a random number between 0 and 7, with bit 2 AND #7 ; set, to give a random number in the range 4 to 7, ORA #4 ; which we use as the x-coordinate of the first vertical ; line in the hangar wall LDY #0 ; Set Y = 0 so the call to DrawHangarWallLine starts ; drawing the wall lines in the first tile of the screen ; row, at the left edge of the screen .hang3 JSR DrawHangarWallLine ; Draw a vertical wall line at x-coordinate A CLC ; Add 10 to A ADC #10 BCS hang4 ; If adding 10 made the addition overflow then we have ; fallen off the right edge of the screen, so jump to ; hang4 to return from the subroutine CMP #248 ; Loop back until we have drawn lines all the way to the BCC hang3 ; right edge of the screen, not going further than an ; x-coordinate of 247 .hang4 RTS ; Return from the subroutine
Name: DrawHangarWallLine [Show more] Type: Subroutine Category: Ship hangar Summary: Draw a vertical hangar wall line from top to bottom, stopping when it bumps into existing on-screen content
Context: See this subroutine on its own page References: This subroutine is called as follows: * HANGER calls DrawHangarWallLine
.DrawHangarWallLine STA S ; Store A in S so we can retrieve it when returning ; from the subroutine STY YSAV ; Store Y in YSAV so we can retrieve it when returning ; from the subroutine LSR A ; Set SC2(1 0) = (nameBufferHi 0) + yLookup(Y) + A / 8 LSR A ; LSR A ; where yLookup(Y) uses the (yLookupHi yLookupLo) table CLC ; to convert the pixel y-coordinate in Y into the number ADC yLookupLo,Y ; of the first tile on the row containing the pixel STA SC2 ; LDA nameBufferHi ; Adding nameBufferHi and A / 8 therefore sets SC2(1 0) ADC yLookupHi,Y ; to the address of the entry in the nametable buffer STA SC2+1 ; that contains the tile number for the tile containing ; the pixel at (A, Y), i.e. the start of the line we are ; drawing LDA S ; Set T = S mod 8, which is the pixel column within the AND #7 ; character block at which we want to draw the start of STA T ; 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 T, as we will always want to draw on ; the same pixel column within each character block .hanw1 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 hanw3 ; pattern has not yet been allocated to this entry, so ; jump to hanw3 to allocate a new 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 tile containing the line we are ROL SC+1 ; drawing STA SC 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 LDX T ; Set X to the pixel column within the character block ; at which we want to draw our line, which we stored in ; T above .hanw2 LDA (SC),Y ; We now work out whether the pixel in column X would AND TWOS,X ; overlap with the top edge of the on-screen ship, which ; we do by AND'ing the pixel pattern with the on-screen ; pixel pattern in SC+Y, so if there are any pixels in ; both the pixel pattern and on-screen, they will be set ; in the result BNE hanw5 ; If the result is non-zero then our pixel in column X ; does indeed overlap with the on-screen ship, so we ; need to stop drawing our well line, so jump to hanw5 ; to return from the subroutine ; If we get here then our pixel in column X does not ; overlap with the on-screen ship, so we can draw it 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 y-coordinate in Y so we move down the ; line by one pixel CPY #8 ; If Y <> 8, loop back to hanw2 draw the next pixel as BNE hanw2 ; we haven't yet reached the bottom of the character ; block containing the line's top end JMP hanw4 ; Otherwise we have finished drawing the vertical line ; in this character row, so jump to hanw4 to move down ; to the next row .hanw3 LDA T ; Set A to the pixel column within the character block ; at which we want to draw our line, which we stored in ; T above 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 T, and as T ; 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 .hanw4 ; 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 hanw1 ; And then the high bytes, jumping to hanw1 when we are INC SC2+1 ; done to draw the vertical line on the next row JMP hanw1 .hanw5 LDA S ; Retrieve the value of A we stored above, so A is ; preserved LDY YSAV ; Retrieve the value of Y we stored above, so Y is ; preserved RTS ; Return from the subroutine
Name: HAL3 [Show more] Type: Subroutine Category: Ship hangar Summary: Draw a hangar background line from left to right, stopping when it bumps into existing on-screen content
Context: See this subroutine on its own page References: This subroutine is called as follows: * HANGER calls HAL3

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.
.HAL3 STX R ; Set R to the line length in X STY YSAV ; Store Y in YSAV so we can retrieve it below LSR A ; Set SC2(1 0) = (nameBufferHi 0) + yLookup(Y) + A / 8 LSR A ; LSR A ; where yLookup(Y) uses the (yLookupHi yLookupLo) table CLC ; to convert the pixel y-coordinate in Y into the number ADC yLookupLo,Y ; of the first tile on the row containing the pixel STA SC2 ; LDA nameBufferHi ; Adding nameBufferHi and A / 8 therefore sets SC2(1 0) ADC yLookupHi,Y ; to the address of the entry in the nametable buffer STA SC2+1 ; that contains the tile number for the tile containing ; the pixel at (A, Y), i.e. the start of 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 .hanl1 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 hanl7 ; pattern has not yet been allocated to this entry, so ; jump to hanl7 to allocate a new 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 tile containing the line we are ROL SC+1 ; drawing STA SC LDA (SC),Y ; If the pattern data where we want to draw the line is BEQ hanl4 ; zero, then there is nothing currently on-screen at ; this point, so jump to hanl4 to draw a full 8-pixel ; line into the pattern data for this tile ; There is something on-screen where we want to draw our ; line, so we now draw the line until it bumps into ; what's already on-screen, so the floor line goes right ; up to the edge of the ship in the hangar LDA #%10000000 ; Set A to a pixel byte containing one set pixel at the ; left end of the 8-pixel row, which we can extend to ; the right by one pixel each time until it meets the ; edge of the on-screen ship .hanl2 STA T ; Store the current pixel pattern in T AND (SC),Y ; We now work out whether the pixel pattern in A would ; overlap with the edge of the on-screen ship, which we ; do by AND'ing the pixel pattern with the on-screen ; pixel pattern in SC+Y, so if there are any pixels in ; both the pixel pattern and on-screen, they will be set ; in the result BNE hanl3 ; If the result is non-zero then our pixel pattern in A ; does indeed overlap with the on-screen ship, so this ; is the pattern we want, so jump to hanl3 to draw it ; If we get here then our pixel pattern in A does not ; overlap with the on-screen ship, so we need to extend ; our pattern to the right by one pixel and try again LDA T ; Shift the whole pixel pattern to the right by one SEC ; pixel, shifting a set pixel into the left end (bit 7) ROR A JMP hanl2 ; Jump back to hanl2 to check whether our extended pixel ; pattern has reached the edge of the ship yet .hanl3 LDA T ; Draw our pixel pattern into the pattern buffer, using ORA (SC),Y ; OR logic so it overwrites what's already there and STA (SC),Y ; merges into the existing ship edge LDY YSAV ; Retrieve the value of Y we stored above, so Y is ; preserved RTS ; Return from the subroutine .hanl4 ; If we get here then we can draw a full 8-pixel wide ; horizontal line into the pattern data for the current ; tile, as there is nothing there already LDA #%11111111 ; Set A to a pixel byte containing eight pixels in a row STA (SC),Y ; Store the 8-pixel line in the Y-th entry in the ; pattern buffer .hanl5 DEC R ; Decrement the line length in R BEQ hanl6 ; If we have drawn all R blocks, jump to hanl6 to return ; from the subroutine INC SC2 ; Increment SC2(1 0) to point to the next nametable BNE hanl1 ; entry to the right, starting with the low byte, and ; if the increment didn't wrap the low byte round to ; zero, jump back to hanl1 to draw the next block of the ; horizontal line INC SC2+1 ; The low byte of SC2(1 0) incremented round to zero, so JMP hanl1 ; we also need to increment the high byte before jumping ; back to hanl1 to draw the next block of the horizontal ; line .hanl6 LDY YSAV ; Retrieve the value of Y we stored above, so Y is ; preserved RTS ; Return from the subroutine .hanl7 ; 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 hanl5 ; Jump up to hanl5 to move on to the next character ; block to the right
Name: HAS3 [Show more] Type: Subroutine Category: Ship hangar Summary: Draw a hangar background line from right to left
Context: See this subroutine on its own page References: This subroutine is called as follows: * HANGER calls HAS3

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.
.HAS3 STX R ; Set R to the line length in X STY YSAV ; Store Y in YSAV so we can retrieve it below LSR A ; Set SC2(1 0) = (nameBufferHi 0) + yLookup(Y) + A / 8 LSR A ; LSR A ; where yLookup(Y) uses the (yLookupHi yLookupLo) table CLC ; to convert the pixel y-coordinate in Y into the number ADC yLookupLo,Y ; of the first tile on the row containing the pixel STA SC2 ; LDA nameBufferHi ; Adding nameBufferHi and A / 8 therefore sets SC2(1 0) ADC yLookupHi,Y ; to the address of the entry in the nametable buffer STA SC2+1 ; that contains the tile number for the tile containing ; the pixel at (A, Y), i.e. the start of 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 .hanr1 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 hanr8 ; pattern has not yet been allocated to this entry, so ; jump to hanr8 to allocate a new 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 tile containing the line we are ROL SC+1 ; drawing STA SC LDA (SC),Y ; If the pattern data where we want to draw the line is BEQ hanr5 ; zero, then there is nothing currently on-screen at ; this point, so jump to hanr5 to draw a full 8-pixel ; line into the pattern data for this tile ; There is something on-screen where we want to draw our ; line, so we now draw the line until it bumps into ; what's already on-screen, so the floor line goes right ; up to the edge of the ship in the hangar LDA #%00000001 ; Set A to a pixel byte containing one set pixel at the ; right end of the 8-pixel row, which we can extend to ; the left by one pixel each time until it meets the ; edge of the on-screen ship .hanr2 STA T ; Store the current pixel pattern in T AND (SC),Y ; We now work out whether the pixel pattern in A would ; overlap with the edge of the on-screen ship, which we ; do by AND'ing the pixel pattern with the on-screen ; pixel pattern in SC+Y, so if there are any pixels in ; both the pixel pattern and on-screen, they will be set ; in the result BNE hanr3 ; If the result is non-zero then our pixel pattern in A ; does indeed overlap with the on-screen ship, so this ; is the pattern we want, so jump to hanr3 to draw it ; If we get here then our pixel pattern in A does not ; overlap with the on-screen ship, so we need to extend ; our pattern to the left by one pixel and try again LDA T ; Shift the whole pixel pattern to the left by one SEC ; pixel, shifting a set pixel into the right end (bit 0) ROL A JMP hanr2 ; Jump back to hanr2 to check whether our extended pixel ; pattern has reached the edge of the ship yet .hanr3 LDA T ; Draw our pixel pattern into the pattern buffer, using ORA (SC),Y ; OR logic so it overwrites what's already there and STA (SC),Y ; merges into the existing ship edge .hanr4 LDY YSAV ; Retrieve the value of Y we stored above, so Y is ; preserved RTS ; Return from the subroutine .hanr5 ; If we get here then we can draw a full 8-pixel wide ; horizontal line into the pattern data for the current ; tile, as there is nothing there already LDA #%11111111 ; Set A to a pixel byte containing eight pixels in a row STA (SC),Y ; Store the 8-pixel line in the Y-th entry in the ; pattern buffer .hanr6 DEC R ; Decrement the line length in R BEQ hanr4 ; If we have drawn all R blocks, jump to hanr4 to return ; from the subroutine LDA SC2 ; We now decrement SC2(1 0) to point to the next BNE hanr7 ; nametable entry to the left, so check whether the low ; byte of SC2(1 0) is non-zero, and if so jump to hanr7 ; to decrement it DEC SC2+1 ; Otherwise we also need to decrement the high byte ; before decrementing the low byte round to $FF .hanr7 DEC SC2 ; Decrement the low byte of SC2(1 0) JMP hanr1 ; Jump back to hanr1 to draw the next block of the ; horizontal line .hanr8 ; 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 hanr6 ; Jump up to hanr6 to move on to the next character ; block to the left
Name: HATB [Show more] Type: Variable Category: Ship hangar Summary: Ship hangar group table
Context: See this variable on its own page References: This variable is used as follows: * HALL uses HATB

This table contains groups of ships to show in the ship hangar. A group of ships is shown half the time (the other half shows a solo ship), and each of the four groups is equally likely. The bytes for each ship in the group contain the following information: Byte #0 Non-zero = Ship type to draw 0 = don't draw anything Byte #1 Bits 0-7 = Ship's x_hi Bit 0 = Ship's z_hi (1 if clear, or 2 if set) Byte #2 Bits 0-7 = Ship's z_lo Bit 0 = Ship's x_sign The ship's y-coordinate is calculated in the has1 routine from the size of its targetable area. Ships of type 0 are not shown.
.HATB ; Hangar group for X = 0 ; ; Shuttle (left) and Transporter (right) EQUB 9 ; Ship type = 9 = Shuttle EQUB %01010100 ; x_hi = %01010100 = 84, z_hi = 1 -> x = -84 EQUB %00111011 ; z_lo = %00111011 = 59, x_sign = 1 z = +315 EQUB 10 ; Ship type = 10 = Transporter EQUB %10000010 ; x_hi = %10000010 = 130, z_hi = 1 -> x = +130 EQUB %10110000 ; z_lo = %10110000 = 176, x_sign = 0 z = +432 EQUB 0 ; No third ship EQUB 0 EQUB 0 ; Hangar group for X = 9 ; ; Three cargo canisters (left, far right and forward, ; right) EQUB OIL ; Ship type = OIL = Cargo canister EQUB %01010000 ; x_hi = %01010000 = 80, z_hi = 1 -> x = -80 EQUB %00010001 ; z_lo = %00010001 = 17, x_sign = 1 z = +273 EQUB OIL ; Ship type = OIL = Cargo canister EQUB %11010001 ; x_hi = %11010001 = 209, z_hi = 2 -> x = +209 EQUB %00101000 ; z_lo = %00101000 = 40, x_sign = 0 z = +552 EQUB OIL ; Ship type = OIL = Cargo canister EQUB %01000000 ; x_hi = %01000000 = 64, z_hi = 1 -> x = +64 EQUB %00000110 ; z_lo = %00000110 = 6, x_sign = 0 z = +262 ; Hangar group for X = 18 ; ; Viper (right) and Krait (left) EQUB COPS ; Ship type = COPS = Viper EQUB %01100000 ; x_hi = %01100000 = 96, z_hi = 1 -> x = +96 EQUB %10010000 ; z_lo = %10010000 = 144, x_sign = 0 z = +400 EQUB KRA ; Ship type = KRA = Krait EQUB %00010000 ; x_hi = %00010000 = 16, z_hi = 1 -> x = -16 EQUB %11010001 ; z_lo = %11010001 = 209, x_sign = 1 z = +465 EQUB 0 ; No third ship EQUB 0 EQUB 0 ; Hangar group for X = 27 ; ; Viper (right and forward) and Krait (left) EQUB 16 ; Ship type = 16 = Viper EQUB %01010001 ; x_hi = %01010001 = 81, z_hi = 2 -> x = +81 EQUB %11111000 ; z_lo = %11111000 = 248, x_sign = 0 z = +760 EQUB 19 ; Ship type = 19 = Krait EQUB %01100000 ; x_hi = %01100000 = 96, z_hi = 1 -> x = -96 EQUB %01110101 ; z_lo = %01110101 = 117, x_sign = 1 z = +373 EQUB 0 ; No third ship EQUB 0 EQUB 0
Name: HALL [Show more] Type: Subroutine Category: Ship hangar Summary: Draw the ships in the ship hangar, then draw the hangar
Context: See this subroutine on its own page References: This subroutine is called as follows: * HALL_b1 calls HALL

Half the time this will draw one of the four pre-defined ship hangar groups in HATB, and half the time this will draw a solitary Sidewinder, Mamba, Krait or Adder on a random position. In all cases, the ships will be randomly spun around on the ground so they can face in any direction, and larger ships are drawn higher up off the ground than smaller ships.
.HALL LDA #$00 ; Clear the screen and set the view type in QQ11 to $00 JSR TT66_b0 ; (Space view with no fonts loaded) LDA nmiCounter ; Set the random number seeds to a fairly random state STA RAND+1 ; that's based on the NMI counter (which increments LDA #$86 ; every VBlank, so will be pretty random), the current STA RAND+3 ; system's galactic x-coordinate (QQ0), the high byte LDA QQ0 ; of our combat rank (TALLY+1), and a fixed number $86 STA RAND LDA TALLY+1 STA RAND+2 JSR DORND ; Set A and X to random numbers BPL HA7 ; Jump to HA7 if A is positive (50% chance) AND #3 ; Reduce A to a random number in the range 0-3 STA T ; Set X = A * 8 + A ASL A ; = 9 * A ASL A ; ASL A ; so X is a random number, either 0, 9, 18 or 27 ADC T TAX ; The following double loop calls the HAS1 routine three ; times to display three ships on screen. For each call, ; the values passed to HAS1 in XX15+2 to XX15 are taken ; from the HATB table, depending on the value in X, as ; follows: ; ; * If X = 0, pass bytes #0 to #2 of HATB to HAS1 ; then bytes #3 to #5 ; then bytes #6 to #8 ; ; * If X = 9, pass bytes #9 to #11 of HATB to HAS1 ; then bytes #12 to #14 ; then bytes #15 to #17 ; ; * If X = 18, pass bytes #18 to #20 of HATB to HAS1 ; then bytes #21 to #23 ; then bytes #24 to #26 ; ; * If X = 27, pass bytes #27 to #29 of HATB to HAS1 ; then bytes #30 to #32 ; then bytes #33 to #35 ; ; Note that the values are passed in reverse, so for the ; first call, for example, where we pass bytes #0 to #2 ; of HATB to HAS1, we call HAS1 with: ; ; XX15 = HATB+2 ; XX15+1 = HATB+1 ; XX15+2 = HATB LDY #3 ; Set CNT2 = 3 to act as an outer loop counter going STY CNT2 ; from 3 to 1, so the HAL8 loop is run 3 times .HAL8 LDY #2 ; Set Y = 2 to act as an inner loop counter going from ; 2 to 0 .HAL9 LDA HATB,X ; Copy the X-th byte of HATB to the Y-th byte of XX15, STA XX15,Y ; as described above INX ; Increment X to point to the next byte in HATB DEY ; Decrement Y to point to the previous byte in XX15 BPL HAL9 ; Loop back to copy the next byte until we have copied ; three of them (i.e. Y was 3 before the DEY) TXA ; Store X on the stack so we can retrieve it after the PHA ; call to HAS1 (as it contains the index of the next ; byte in HATB JSR HAS1 ; Call HAS1 to draw this ship in the hangar PLA ; Restore the value of X, so X points to the next byte TAX ; in HATB after the three bytes we copied into XX15 DEC CNT2 ; Decrement the outer loop counter in CNT2 BNE HAL8 ; Loop back to HAL8 to do it 3 times, once for each ship ; in the HATB table LDY #128 ; Set Y = 128 to send as byte #2 of the parameter block ; to the OSWORD 248 command below, to tell the I/O ; processor that there are multiple ships in the hangar BNE HA9 ; Jump to HA9 to display the ship hangar (this BNE is ; effectively a JMP as Y is never zero) .HA7 ; If we get here, A is a positive random number in the ; range 0-127 LSR A ; Set XX15+1 = A / 2 (random number 0-63) STA XX15+1 JSR DORND ; Set XX15 = random number 0-255 STA XX15 JSR DORND ; Set XX15+2 = #SH3 + random number 0-3 AND #3 ; ADC #SH3 ; which is the ship type of a Sidewinder, Mamba, Krait STA XX15+2 ; or Adder JSR HAS1 ; Call HAS1 to draw this ship in the hangar, with the ; following properties: ; ; * Random x-coordinate from -63 to +63 ; ; * Randomly chosen Sidewinder, Mamba, Krait or Adder ; ; * Random z-coordinate from +256 to +639 LDY #0 ; Set Y = 0 to use in the following instruction, to tell ; the hangar-drawing routine that there is just one ship ; in the hangar, so it knows not to draw between the ; ships .HA9 STY HANGFLAG ; Store Y in HANGFLAG to specify whether there are ; multiple ships in the hangar JSR HANGER ; Call HANGER to draw the hangar background LDA #0 ; Tell the NMI handler to send pattern entries from STA firstPattern ; pattern 0 in the buffer LDA #80 ; Tell the NMI handler to only clear nametable entries STA maxNameTileToClear ; up to tile 80 * 8 = 640 (i.e. up to the end of tile ; row 19) JMP UpdateHangarView ; Update the hangar view on-screen by sending the data ; to the PPU, returning from the subroutine using a tail ; call
Name: ZINF_b1 [Show more] Type: Subroutine Category: Universe Summary: Reset the INWK workspace and orientation vectors Deep dive: Orientation vectors
Context: See this subroutine on its own page References: This subroutine is called as follows: * HAS1 calls ZINF_b1

Zero-fill the INWK ship workspace and reset the orientation vectors, with nosev pointing out of the screen, towards us.
Returns: Y Y is set to $FF
.ZINF_b1 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 #NI%-1 ; There are NI% bytes in the INWK workspace, so set a ; counter in Y so we can loop through them LDA #0 ; Set A to 0 so we can zero-fill the workspace .ZI1_b1 STA INWK,Y ; Zero the Y-th byte of the INWK workspace DEY ; Decrement the loop counter BPL ZI1_b1 ; Loop back for the next byte, ending when we have ; zero-filled the last byte at INWK, which leaves Y ; with a value of $FF ; Finally, we reset the orientation vectors as follows: ; ; sidev = (1, 0, 0) ; roofv = (0, 1, 0) ; nosev = (0, 0, -1) ; ; 96 * 256 ($6000) represents 1 in the orientation ; vectors, while -96 * 256 ($E000) represents -1. We ; already set the vectors to zero above, so we just ; need to set up the high bytes of the diagonal values ; and we're done. The negative nosev makes the ship ; point towards us, as the z-axis points into the screen 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 #96 ; Set A to represent a 1 (in vector terms) STA INWK+18 ; Set byte #18 = roofv_y_hi = 96 = 1 STA INWK+22 ; Set byte #22 = sidev_x_hi = 96 = 1 ORA #128 ; Flip the sign of A to represent a -1 STA INWK+14 ; Set byte #14 = nosev_z_hi = -96 = -1 RTS ; Return from the subroutine
Name: HAS1 [Show more] Type: Subroutine Category: Ship hangar Summary: Draw a ship in the ship hangar
Context: See this subroutine on its own page References: This subroutine is called as follows: * HALL calls HAS1

The ship's position within the hangar is determined by the arguments and the size of the ship's targetable area, as follows: * The x-coordinate is (x_sign x_hi 0) from the arguments, so the ship can be left of centre or right of centre * The y-coordinate is negative and is lower down the screen for smaller ships, so smaller ships are drawn closer to the ground (because they are) * The z-coordinate is positive, with both z_hi (which is 1 or 2) and z_lo coming from the arguments
Arguments: XX15 Bits 0-7 = Ship's z_lo Bit 0 = Ship's x_sign XX15+1 Bits 0-7 = Ship's x_hi Bit 0 = Ship's z_hi (1 if clear, or 2 if set) XX15+2 Non-zero = Ship type to draw 0 = Don't draw anything
.HAS1 JSR ZINF_b1 ; Call ZINF to reset the INWK ship workspace and reset ; the orientation vectors, with nosev pointing out of ; the screen, so this puts the ship flat on the ; horizontal deck (the y = 0 plane) with its nose ; pointing towards us LDA XX15 ; Set z_lo = XX15 STA INWK+6 LSR A ; Set the sign bit of x_sign to bit 0 of A ROR INWK+2 LDA XX15+1 ; Set x_hi = XX15+1 STA INWK LSR A ; Set z_hi = 1 + bit 0 of XX15+1 LDA #1 ADC #0 STA INWK+7 LDA #%10000000 ; Set bit 7 of y_sign, so y is negative STA INWK+5 STA RAT2 ; Set RAT2 = %10000000, so the yaw calls in HAL5 below ; are negative LDA #$B ; This instruction is left over from the other versions STA INWK+34 ; of Elite, which store the ship line heap pointer in ; INWK(34 33), but the NES version doesn't have a ship ; line heap, so this instruction has no effect (INWK+34 ; is reused in NES Elite for the ship's explosion cloud ; counter, but that is ignored by the hangar code) JSR DORND ; We now perform a random number of small angle (3.6 STA XSAV ; degree) rotations to spin the ship on the deck while ; keeping it flat on the deck (a bit like spinning a ; bottle), so we set XSAV to a random number between 0 ; and 255 for the number of small yaw rotations to ; perform, so the ship could be pointing in any ; direction by the time we're done .HAL5 LDX #21 ; Rotate (sidev_x, nosev_x) by a small angle (yaw) LDY #9 JSR MVS5_b0 LDX #23 ; Rotate (sidev_y, nosev_y) by a small angle (yaw) LDY #11 JSR MVS5_b0 LDX #25 ; Rotate (sidev_z, nosev_z) by a small angle (yaw) LDY #13 JSR MVS5_b0 DEC XSAV ; Decrement the yaw counter in XSAV BNE HAL5 ; Loop back to yaw a little more until we have yawed ; by the number of times in XSAV LDY XX15+2 ; Set Y = XX15+2, the ship type of the ship we need to ; draw BEQ HA1 ; If Y = 0, return from the subroutine (as HA1 contains ; an RTS) TYA ; Set X = 2 * Y ASL A TAX LDA XX21-2,X ; Set XX0(1 0) to the X-th address in the ship blueprint STA XX0 ; address lookup table at XX21, so XX0(1 0) now points LDA XX21-1,X ; to the blueprint for the ship we need to draw STA XX0+1 BEQ HA1 ; If the high byte of the blueprint address is 0, then ; this is not a valid blueprint address, so return from ; the subroutine (as HA1 contains an RTS) LDY #1 ; Set Q = ship byte #1 LDA (XX0),Y STA Q INY ; Set R = ship byte #2 LDA (XX0),Y ; STA R ; so (R Q) contains the ship's targetable area, which is ; a square number JSR LL5 ; Set Q = SQRT(R Q) LDA #100 ; Set y_lo = (100 - Q) / 2 SBC Q ; LSR A ; so the bigger the ship's targetable area, the smaller STA INWK+3 ; the magnitude of the y-coordinate, so because we set ; y_sign to be negative above, this means smaller ships ; are drawn lower down, i.e. closer to the ground, while ; larger ships are drawn higher up, as you would expect JSR TIDY ; Call TIDY to tidy up the orientation vectors, to ; prevent the ship from getting elongated and out of ; shape due to the imprecise nature of trigonometry ; in assembly language JMP LL9 ; Jump to LL9 to display the ship and return from the ; subroutine using a tail call .HA1 RTS ; Return from the subroutine
Name: TIDY [Show more] Type: Subroutine Category: Maths (Geometry) Summary: Orthonormalise the orientation vectors for a ship Deep dive: Tidying orthonormal vectors Orientation vectors
Context: See this subroutine on its own page References: This subroutine is called as follows: * HAS1 calls TIDY * TIDY_b1 calls TIDY

This routine orthonormalises the orientation vectors for a ship. This means making the three orientation vectors orthogonal (perpendicular to each other), and normal (so each of the vectors has length 1). We do this because we use the small angle approximation to rotate these vectors in space. It is not completely accurate, so the three vectors tend to get stretched over time, so periodically we tidy the vectors with this routine to ensure they remain as orthonormal as possible.
.TI2 ; Called from below with A = 0, X = 0, Y = 4 when ; nosev_x and nosev_y are small, so we assume that ; nosev_z is big TYA ; A = Y = 4 LDY #2 JSR TIS3 ; Call TIS3 with X = 0, Y = 2, A = 4, to set roofv_z = STA INWK+20 ; -(nosev_x * roofv_x + nosev_y * roofv_y) / nosev_z JMP TI3 ; Jump to TI3 to keep tidying .TI1 ; Called from below with A = 0, Y = 4 when nosev_x is ; small TAX ; Set X = A = 0 LDA XX15+1 ; Set A = nosev_y, and if the top two magnitude bits AND #%01100000 ; are both clear, jump to TI2 with A = 0, X = 0, Y = 4 BEQ TI2 LDA #2 ; Otherwise nosev_y is big, so set up the index values ; to pass to TIS3 JSR TIS3 ; Call TIS3 with X = 0, Y = 4, A = 2, to set roofv_y = STA INWK+18 ; -(nosev_x * roofv_x + nosev_z * roofv_z) / nosev_y JMP TI3 ; Jump to TI3 to keep tidying .TIDY 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+10 ; Set (XX15, XX15+1, XX15+2) = nosev STA XX15 LDA INWK+12 STA XX15+1 LDA INWK+14 STA XX15+2 JSR NORM ; Call NORM to normalise the vector in XX15, i.e. nosev LDA XX15 ; Set nosev = (XX15, XX15+1, XX15+2) STA INWK+10 LDA XX15+1 STA INWK+12 LDA XX15+2 STA INWK+14 LDY #4 ; Set Y = 4 LDA XX15 ; Set A = nosev_x, and if the top two magnitude bits AND #%01100000 ; are both clear, jump to TI1 with A = 0, Y = 4 BEQ TI1 LDX #2 ; Otherwise nosev_x is big, so set up the index values LDA #0 ; to pass to TIS3 JSR TIS3 ; Call TIS3 with X = 2, Y = 4, A = 0, to set roofv_x = STA INWK+16 ; -(nosev_y * roofv_y + nosev_z * roofv_z) / nosev_x .TI3 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+16 ; Set (XX15, XX15+1, XX15+2) = roofv STA XX15 LDA INWK+18 STA XX15+1 LDA INWK+20 STA XX15+2 JSR NORM ; Call NORM to normalise the vector in XX15, i.e. roofv LDA XX15 ; Set roofv = (XX15, XX15+1, XX15+2) STA INWK+16 LDA XX15+1 STA INWK+18 LDA XX15+2 STA INWK+20 LDA INWK+12 ; Set Q = nosev_y STA Q LDA INWK+20 ; Set A = roofv_z JSR MULT12 ; Set (S R) = Q * A = nosev_y * roofv_z LDX INWK+14 ; Set X = nosev_z LDA INWK+18 ; Set A = roofv_y JSR TIS1 ; Set (A ?) = (-X * A + (S R)) / 96 ; = (-nosev_z * roofv_y + nosev_y * roofv_z) / 96 ; ; This also sets Q = nosev_z EOR #%10000000 ; Set sidev_x = -A STA INWK+22 ; = (nosev_z * roofv_y - nosev_y * roofv_z) / 96 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+16 ; Set A = roofv_x JSR MULT12 ; Set (S R) = Q * A = nosev_z * roofv_x LDX INWK+10 ; Set X = nosev_x LDA INWK+20 ; Set A = roofv_z JSR TIS1 ; Set (A ?) = (-X * A + (S R)) / 96 ; = (-nosev_x * roofv_z + nosev_z * roofv_x) / 96 ; ; This also sets Q = nosev_x EOR #%10000000 ; Set sidev_y = -A STA INWK+24 ; = (nosev_x * roofv_z - nosev_z * roofv_x) / 96 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+18 ; Set A = roofv_y JSR MULT12 ; Set (S R) = Q * A = nosev_x * roofv_y LDX INWK+12 ; Set X = nosev_y LDA INWK+16 ; Set A = roofv_x JSR TIS1 ; Set (A ?) = (-X * A + (S R)) / 96 ; = (-nosev_y * roofv_x + nosev_x * roofv_y) / 96 EOR #%10000000 ; Set sidev_z = -A STA INWK+26 ; = (nosev_y * roofv_x - nosev_x * roofv_y) / 96 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 A = 0 so we can clear the low bytes of the ; orientation vectors LDX #14 ; We want to clear the low bytes, so start from sidev_y ; at byte #9+14 (we clear all except sidev_z_lo, though ; I suspect this is in error and that X should be 16) .TIL1 STA INWK+9,X ; Set the low byte in byte #9+X to zero DEX ; Set X = X - 2 to jump down to the next low byte DEX BPL TIL1 ; Loop back until we have zeroed all the low bytes RTS ; Return from the subroutine
Name: TIS3 [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate -(nosev_1 * roofv_1 + nosev_2 * roofv_2) / nosev_3
Context: See this subroutine on its own page References: This subroutine is called as follows: * TIDY calls TIS3

Calculate the following expression: A = -(nosev_1 * roofv_1 + nosev_2 * roofv_2) / nosev_3 where 1, 2 and 3 are x, y, or z, depending on the values of X, Y and A. This routine is called with the following values: X = 0, Y = 2, A = 4 -> A = -(nosev_x * roofv_x + nosev_y * roofv_y) / nosev_z X = 0, Y = 4, A = 2 -> A = -(nosev_x * roofv_x + nosev_z * roofv_z) / nosev_y X = 2, Y = 4, A = 0 -> A = -(nosev_y * roofv_y + nosev_z * roofv_z) / nosev_x
Arguments: X Index 1 (0 = x, 2 = y, 4 = z) Y Index 2 (0 = x, 2 = y, 4 = z) A Index 3 (0 = x, 2 = y, 4 = z)
.TIS3 STA P+2 ; Store P+2 in A for later LDA INWK+10,X ; Set Q = nosev_x_hi (plus X) STA Q LDA INWK+16,X ; Set A = roofv_x_hi (plus X) JSR MULT12 ; Set (S R) = Q * A ; = nosev_x_hi * roofv_x_hi LDX INWK+10,Y ; Set Q = nosev_x_hi (plus Y) STX Q LDA INWK+16,Y ; Set A = roofv_x_hi (plus Y) JSR MAD ; Set (A X) = Q * A + (S R) ; = (nosev_x,X * roofv_x,X) + ; (nosev_x,Y * roofv_x,Y) STX P ; Store low byte of result in P, so result is now in ; (A P) LDY P+2 ; Set Q = roofv_x_hi (plus argument A) LDX INWK+10,Y STX Q EOR #%10000000 ; Flip the sign of A ; Fall through into DIVDT to do: ; ; (P+1 A) = (A P) / Q ; ; = -((nosev_x,X * roofv_x,X) + ; (nosev_x,Y * roofv_x,Y)) ; / nosev_x,A
Name: DVIDT [Show more] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (P+1 A) = (A P) / Q
Context: See this subroutine on its own page References: No direct references to this subroutine in this source file

Calculate the following integer division between sign-magnitude numbers: (P+1 A) = (A P) / Q This uses the same shift-and-subtract algorithm as TIS2.
.DVIDT STA P+1 ; Set P+1 = A, so P(1 0) = (A P) EOR Q ; Set T = the sign bit of A EOR Q, so it's 1 if A and Q AND #%10000000 ; have different signs, i.e. it's the sign of the result STA T ; of A / Q LDA #0 ; Set A = 0 for us to build a result LDX #16 ; Set a counter in X to count the 16 bits in P(1 0) ASL P ; Shift P(1 0) left ROL P+1 ASL Q ; Clear the sign bit of Q the C flag at the same time LSR Q .DVL2 ROL A ; Shift A to the left CMP Q ; If A < Q skip the following subtraction BCC P%+4 SBC Q ; Set A = A - Q ; ; Going into this subtraction we know the C flag is ; set as we passed through the BCC above, and we also ; know that A >= Q, so the C flag will still be set once ; we are done ROL P ; Rotate P(1 0) to the left, and catch the result bit ROL P+1 ; into the C flag (which will be a 0 if we didn't ; do the subtraction, or 1 if we did) DEX ; Decrement the loop counter BNE DVL2 ; Loop back for the next bit until we have done all 16 ; bits of P(1 0) LDA P ; Set A = P so the low byte is in the result in A ORA T ; Set A to the correct sign bit that we set in T above RTS ; Return from the subroutine
Name: SCAN [Show more] Type: Subroutine Category: Dashboard Summary: Display the current ship on the scanner Deep dive: Sprite usage in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * SCAN_b1 calls SCAN

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: INWK The ship's data block
.scan1 ; If we jump here, Y is set to the offset of the first ; sprite for this ship on the scanner, so the three ; sprites are at addresses ySprite0 + Y, ySprite1 + Y ; and ySprite2 + Y LDA #240 ; Hide sprites Y to Y+2 in the sprite buffer by setting STA ySprite0,Y ; their y-coordinates to 240, which is off the bottom STA ySprite1,Y ; of the screen STA ySprite2,Y ; ; So this removes the ship's scanner sprites from the ; scanner .scan2 RTS ; Return from the subroutine .SCAN LDA QQ11 ; If this is not the space view (i.e. QQ11 is non-zero) BNE scan2 ; then jump to scan2 to return from the subroutine as ; there is no scanner to update LDX TYPE ; Fetch the ship's type from TYPE into X BMI scan2 ; If this is the planet or the sun, then the type will ; have bit 7 set and we don't want to display it on the ; scanner, so jump to scan2 to return from the ; subroutine as there is nothing to draw LDA INWK+33 ; Set A to ship byte #33, which contains the number of ; this ship on the scanner BEQ scan2 ; If A = 0 then this ship is not being shown on the ; scanner, so jump to scan2 to return from the ; subroutine as there is nothing to draw TAX ; Set X to the number of this ship on the scanner ASL A ; Set Y = (A * 2 + A) * 4 + 44 ADC INWK+33 ; = 44 + A * 3 * 4 ASL A ; ASL A ; This gives us the offset of the first sprite for this ADC #44 ; ship on the scanner within the sprite buffer, as each TAY ; ship has three sprites allocated to it, and there are ; four bytes per sprite in the buffer, and the first ; scanner sprite is sprite 11 (which is at offset 44 in ; the buffer) ; ; We will refer to the sprites that we will use to draw ; the ship on the scanner as sprites Y, Y+1 and Y+2, ; just to keep things simple LDA scannerColour,X ; Set A to the scanner colour for this ship, which was ; set to a sprite palette number in the NWSHP routine ; when the ship was added to the local bubble of ; universe STA attrSprite0,Y ; Set the attributes for sprite Y to the value in A, ; so the sprite's attributes are: ; ; * Bits 0-1 = sprite palette number in A ; * Bit 5 clear = show in front of background ; * Bit 6 clear = do not flip horizontally ; * Bit 7 clear = do not flip vertically ; ; This ensures that the ship appears on the scanner in ; the correct colour for the ship type ; ; We will copy these attributes for use in the other two ; sprites later ; The following algorithm is the same as the FAROF2 ; routine, which calculates the distance from our ship ; to the point (x_hi, y_hi, z_hi) to see if the ship is ; close enough to be visible on the scanner ; ; Note that it actually calculates half the distance to ; the point (i.e. 0.5 * |x y z|) as this will ensure the ; result fits into one byte LDA INWK+1 ; If x_hi >= y_hi, jump to scan3 to skip the following CMP INWK+4 ; instruction, leaving A containing the higher of the BCS scan3 ; two values LDA INWK+4 ; Set A = y_hi, which is the higher of the two values, ; so by this point we have: ; ; A = max(x_hi, y_hi) .scan3 CMP INWK+7 ; If A >= z_hi, jump to scan4 to skip the following BCS scan4 ; instruction, leaving A containing the higher of the ; two values LDA INWK+7 ; Set A = z_hi, which is the higher of the two values, ; so by this point we have: ; ; A = max(x_hi, y_hi, z_hi) .scan4 CMP #64 ; If A >= 64 then at least one of x_hi, y_hi and z_hi is BCS scan1 ; greater or equal to 64, so jump to scan1 to hide the ; ship's scanner sprites and return from the subroutine, ; as the ship is too far away to appear on the scanner STA SC2 ; Otherwise set SC2 = max(x_hi, y_hi, z_hi) ; ; Let's call this max(x, y, z) LDA INWK+1 ; Set A = x_hi + y_hi + z_hi ADC INWK+4 ; ADC INWK+7 ; Let's call this x + y + z ; ; There is a risk that the addition will overflow here, ; but presumably this isn't an issue BCS scan1 ; If the addition overflowed then A > 255, so jump to ; scan1 to hide the ship's scanner sprites and return ; from the subroutine, as the ship is too far away to ; appear on the scanner SEC ; Set SC2+1 = A - SC2 / 4 SBC SC2 ; = (x + y + z - max(x, y, z)) / 4 LSR A LSR A STA SC2+1 LSR A ; Set A = (SC2+1 / 4) + SC2+1 + SC2 LSR A ; = 5/4 * SC2+1 + SC2 ADC SC2+1 ; = 5 * (x + y + z - max(x, y, z)) / (8 * 4) ADC SC2 ; + 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 #64 ; If A >= 64 then jump to scan1 to hide the ship's BCS scan1 ; scanner sprites and return from the subroutine, as the ; ship is too far away to appear on the scanner ; We now calculate the position of the ship on the ; scanner, starting with the x-coordinate (see the deep ; dive on "The 3D scanner" for an explanation of the ; following) LDA INWK+1 ; Set A = x_hi CLC ; Clear the C flag so we can do addition below LDX INWK+2 ; Set X = x_sign BPL scan5 ; If x_sign is positive, skip the following EOR #%11111111 ; x_sign is negative, so flip the bits in A and add ADC #1 ; 1 to make it a negative number .scan5 ADC #124 ; Set SC2 = 124 + (x_sign x_hi) STA SC2 ; ; So this gives us the x-coordinate of the ship on the ; scanner ; Next, we convert the z_hi coordinate of the ship into ; the y-coordinate of the base of the ship's stick, ; like this: ; ; SC2+1 = 199 - (z_sign z_hi) / 4 LDA INWK+7 ; Set A = z_hi / 4 LSR A LSR A CLC ; Clear the C flag for the addition below LDX INWK+8 ; Set X = z_sign BMI scan6 ; If z_sign is negative, skip the following so we add ; the magnitude of x_hi (which is the same as ; subtracting a negative x_hi) EOR #%11111111 ; z_sign is positive, so flip the bits in A and set the SEC ; C flag. This makes A negative using two's complement, ; and as we are about to do an ADC, the SEC effectively ; add the 1 we need, giving A = -x_hi .scan6 ADC #199+YPAL ; Set SC2+1 = 199 + A to give us the y-coordinate of the STA SC2+1 ; base of the ship's stick ; Finally, we calculate Y1 to represent the height of ; stick as: ; ; Y1 = min(y_hi, 47) LDA INWK+4 ; Set A = y_hi CMP #48 ; If A < 48, jump to scan7 to skip the following BCC scan7 ; instruction LDA #47 ; Set A = 47, so A contains y_hi capped to a maximum ; value of 47, i.e. A = min(y_hi, 47) .scan7 LSR A ; Set Y1 = A = min(y_hi, 47) STA Y1 CLC ; Clear the C flag (though this has no effect) ; We now have the following data: ; ; * SC2 is the x-coordinate of the ship on the scanner ; ; * SC2+1 is the y-coordinate of the base of the ; ship's stick ; ; * Y1 is the height of the stick ; ; So now we draw the ship on the scanner, with the first ; step being to work out whether we should draw the ship ; above or below the 3D ellipse (and therefore in front ; of or behind the background tiles that make up the 3D ; ellipse on-screen) BEQ scan8 ; If A = 0 then y_hi must be 0, so the ship is exactly ; on the 3D ellipse on the scanner (not above or below ; it), so jump to scan8 to draw the ship in front of ; the ellipse background LDX INWK+5 ; If y_sign is positive then the ship is above the 3D BPL scan8 ; ellipse on the scanner, so jump to scan8 to draw the ; ship in front of the ellipse background JMP scan12 ; Otherwise the ship is below the 3D ellipse on the ; scanner, so jump to scan12 to draw the ship behind ; the ellipse background .scan8 ; If we get here then we draw the ship in front of the ; 3D ellipse LDA SC2+1 ; Set SC2+1 = SC2+1 - 8 SEC ; SBC #8 ; This subtracts 8 from the y-coordinate of the bottom STA SC2+1 ; of the stick, and ensures that the bottom of the stick ; looks as if it is touching the 3D ellipse ; The ship is drawn on the scanner using up to three ; sprites - sprites Y, Y+1 and Y+2 ; ; Sprite Y is the end of the stick that's furthest from ; the ship dot (i.e. the "bottom" of the stick which ; appears to touch the 3D ellipse) ; ; Sprite Y+1 is the middle part of the stick ; ; Sprite Y+2 is the big ship dot at the end of the stick ; (i.e. the "top" of the stick) ; ; If a stick is full height, then we show all three ; sprites, if a stick is medium height we display ; sprites Y+1 and Y+2, and if a stick is small we only ; display sprite Y+2 ; ; We always draw sprite Y+2, so first we concentrate on ; drawing sprites Y and Y+1, if required LDA Y1 ; If Y1 < 16 then the stick is either medium or small, CMP #16 ; so jump to scan9 to skip the following BCC scan9 ; If we get here then the stick is full height, so we ; draw both sprite Y and sprite Y+1 LDA SC2 ; Set the x-coordinate of sprite Y to SC2 STA xSprite0,Y STA xSprite1,Y ; Set the x-coordinate of sprite Y+1 to SC2 LDA SC2+1 ; Set the y-coordinate of sprite Y to SC2+1, as SC2+1 STA ySprite0,Y ; contains the y-coordinate of the bottom of the stick SEC ; Set the y-coordinate of sprite Y+1 to SC2+1 - 8, which SBC #8 ; is eight pixels higher up the screen then sprite Y STA ySprite1,Y ; ; This stacks the sprites one above the other, as each ; sprite is eight pixels LDA attrSprite0,Y ; Clear bits 2 to 7 of the attributes for sprite Y to AND #%00000011 ; ensure that the following attributes are set: STA attrSprite0,Y ; ; * Bit 5 clear = show in front of background ; * Bit 6 clear = do not flip horizontally ; * Bit 7 clear = do not flip vertically ; ; This makes sure we display the sprite in front of the ; 3D ellipse, as the ship is above the ellipse in space STA attrSprite1,Y ; Set the attributes for sprite Y+1 to the same as those ; for sprite Y LDA SC2+1 ; Set SC2+1 = SC2+1 - 16 SBC #16 ; STA SC2+1 ; This subtracts 16 from the y-coordinate of the bottom ; of the stick to give us the y-coordinate of the top ; sprite in the stick BNE scan11 ; Jump to scan11 to draw sprite Y+2 at the top of the ; stick (this BNE is effectively a JMP as A is never ; zero) .scan9 ; If we get here then the stick is either medium or ; small height CMP #8 ; If Y1 < 8 then the stick is small, so jump to scan10 BCC scan10 ; to skip the following ; If we get here then the stick is medium height, so we ; draw sprite Y+1 and hide sprite Y LDA #240 ; Hide sprite Y by setting its y-coordinate to 240, STA ySprite0,Y ; which moves it off the bottom of the screen LDA SC2 ; Set the x-coordinate of sprite Y+1 to SC2 STA xSprite1,Y LDA SC2+1 ; Set the y-coordinate of sprite Y+1 to SC2+1, as SC2+1 STA ySprite1,Y ; contains the y-coordinate of the bottom of the stick ; (which is the middle sprite in a medium height stick) LDA attrSprite0,Y ; Clear bits 2 to 7 of the attributes for sprite Y+1 to AND #%00000011 ; ensure that the following attributes are set: STA attrSprite1,Y ; ; * Bit 5 clear = show in front of background ; * Bit 6 clear = do not flip horizontally ; * Bit 7 clear = do not flip vertically ; ; This makes sure we display the sprite in front of the ; 3D ellipse, as the ship is above the ellipse in space ; ; Note that we do this by copying the attributes we set ; up for sprite Y into sprite Y+1, clearing the bits as ; we do so LDA SC2+1 ; Set SC2+1 = SC2+1 - 8 SBC #8 ; STA SC2+1 ; This subtracts 8 from the y-coordinate of the bottom ; of the stick to give us the y-coordinate of the top ; sprite in the stick BNE scan11 ; Jump to scan11 to draw sprite Y+2 at the top of the ; stick (this BNE is effectively a JMP as A is never ; zero) .scan10 ; If we get here then the stick is small, so we hide ; sprites Y and Y+1, leaving just sprite Y+2 visible on ; the scanner LDA #240 ; Hide sprites Y and Y+1 from the scanner by setting STA ySprite0,Y ; their y-coordinates to 240, which moves them off the STA ySprite1,Y ; bottom of the screen .scan11 ; We now draw sprite Y+2, which contains the ship dot at ; the end of the stick LDA Y1 ; Set A = Y1 mod 8 AND #7 ; ; This gives us the height of the top part of the stick, ; as there are eight pixels in each sprite making up the ; stick, so this is the remainder after sprites Y and ; Y+1 are taken away CLC ; Set the pattern number for sprite Y+2 to 219 + A ADC #219 ; STA pattSprite2,Y ; Sprites 219 to 226 contain ship dots with trailing ; sticks, starting with the dot at the bottom of the ; pattern (in pattern 219) up to the dot at the top of ; the pattern (in pattern 226), so this sets the tile ; pattern for sprite Y+2 to the dot height given in A, ; which is the correct pattern for the top of the ship's ; stick LDA attrSprite0,Y ; Clear bits 2 to 7 of the attributes for sprite Y+1 to AND #%00000011 ; ensure that the following attributes are set: STA attrSprite2,Y ; ; * Bit 5 clear = show in front of background ; * Bit 6 clear = do not flip horizontally ; * Bit 7 clear = do not flip vertically ; ; This makes sure we display the sprite in front of the ; 3D ellipse, as the ship is above the ellipse in space ; ; Note that we do this by copying the attributes we set ; up for sprite Y into sprite Y+2, clearing the bits as ; we do so LDA SC2 ; Set the x-coordinate of sprite Y+2 to SC2 STA xSprite2,Y LDA SC2+1 ; Set the y-coordinate of sprite Y+2 to SC2+1, which by STA ySprite2,Y ; now contains the y-coordinate of the top of the stick, RTS ; Return from the subroutine .scan12 ; If we get here then we draw the ship behind the 3D ; ellipse ; ; We jump here with A set to Y1, the height of the ; stick, which we now need to clip to ensure it doesn't ; fall off the bottom of the screen CLC ; Set A = Y1 + SC2+1 ADC SC2+1 CMP #220+YPAL ; If A < 220, jump to scan13 to skip the following BCC scan13 ; instruction LDA #220+YPAL ; Set A = 220, so A is a maximum of 220 .scan13 SEC ; Set Y1 = A - SC2+1 SBC SC2+1 ; STA Y1 ; So this leaves Y1 alone unless Y1 + SC2+1 >= 220, in ; which case Y1 is clipped so that Y1 + SC2+1 = 220 (so ; this moves the "top" of the stick so that the ship dot ; doesn't go off the bottom of the screen) ; The ship is drawn on the scanner using up to three ; sprites - sprites Y, Y+1 and Y+2 ; ; Sprite Y is the end of the stick that's furthest from ; the ship dot (i.e. the "bottom" of the stick which ; appears to touch the 3D ellipse, though as we are ; drawing the stick below the ellipse, this is the part ; of the stick that is highest up the screen) ; ; Sprite Y+1 is the middle part of the stick ; ; Sprite Y+2 is the big ship dot at the end of the stick ; (i.e. the "top" of the stick, though as we are ; drawing the stick below the ellipse, this is the part ; of the stick that is furthest down the screen) ; ; If a stick is full height, then we show all three ; sprites, if a stick is medium height we display ; sprites Y+1 and Y+2, and if a stick is small we only ; display sprite Y+2 ; ; We always draw sprite Y+2, so first we concentrate on ; drawing sprites Y and Y+1, if required CMP #16 ; If Y1 < 16 then the stick is either medium or small, BCC scan14 ; so jump to scan14 to skip the following ; If we get here then the stick is full height, so we ; draw both sprite Y and sprite Y+1 LDA SC2 ; Set the x-coordinate of sprite Y to SC2 STA xSprite0,Y STA xSprite1,Y ; Set the x-coordinate of sprite Y+1 to SC2 LDA SC2+1 ; Set the y-coordinate of sprite Y to SC2+1, as SC2+1 STA ySprite0,Y ; contains the y-coordinate of the "bottom" of the stick CLC ; Set the y-coordinate of sprite Y+1 to SC2+1 + 8, which ADC #8 ; is eight pixels lower down the screen then sprite Y STA ySprite1,Y ; ; This stacks the sprites one below the other, as each ; sprite is eight pixels LDA attrSprite0,Y ; Set bit 5 of the attributes for sprite Y to ensure ORA #%00100000 ; that the following attribute is set: STA attrSprite0,Y ; ; * Bit 5 set = show behind background ; ; This makes sure we display the sprite behind the ; 3D ellipse, as the ship is below the ellipse in space STA attrSprite1,Y ; Set the attributes for sprite Y+1 to the same as those ; for sprite Y LDA SC2+1 ; Set SC2+1 = SC2+1 + 16 CLC ; ADC #16 ; This adds 16 to the y-coordinate of the "bottom" of STA SC2+1 ; the stick to give us the y-coordinate of the "top" ; sprite in the stick BNE scan16 ; Jump to scan16 to draw sprite Y+2 at the "top" of the ; stick (this BNE is effectively a JMP as A is never ; zero) .scan14 ; If we get here then the stick is either medium or ; small height CMP #8 ; If Y1 < 8 then the stick is small, so jump to scan15 BCC scan15 ; to skip the following ; If we get here then the stick is medium height, so we ; draw sprite Y+1 and hide sprite Y LDA #240 ; Hide sprite Y by setting its y-coordinate to 240, STA ySprite0,Y ; which moves it off the bottom of the screen LDA SC2 ; Set the x-coordinate of sprite Y+1 to SC2 STA xSprite1,Y LDA SC2+1 ; Set the y-coordinate of sprite Y+1 to SC2+1, as SC2+1 STA ySprite1,Y ; contains the y-coordinate of the bottom of the stick ; (which is the middle sprite in a medium height stick) LDA attrSprite0,Y ; Set bit 5 of the attributes for sprite Y to ensure ORA #%00100000 ; that the following attribute is set: STA attrSprite1,Y ; ; * Bit 5 set = show behind background ; ; This makes sure we display the sprite behind the ; 3D ellipse, as the ship is below the ellipse in space ; ; Note that we do this by copying the attributes we set ; up for sprite Y into sprite Y+1, clearing the bits as ; we do so LDA SC2+1 ; Set SC2+1 = SC2+1 + 8 ADC #7 ; STA SC2+1 ; This adds 8 to the y-coordinate of the "bottom" of the ; stick to give us the y-coordinate of the "top" sprite ; in the stick ; ; The addition works as we know the C flag is set, as we ; passed through a BCS above, so the ADC #7 actually ; adds 8 BNE scan16 ; Jump to scan16 to draw sprite Y+2 at the "top" of the ; stick (this BNE is effectively a JMP as A is never ; zero) .scan15 ; If we get here then the stick is small, so we hide ; sprites Y and Y+1, leaving just sprite Y+2 visible on ; the scanner LDA #240 ; Hide sprites Y and Y+1 from the scanner by setting STA ySprite0,Y ; their y-coordinates to 240, which moves them off the STA ySprite1,Y ; bottom of the screen .scan16 ; We now draw sprite Y+2, which contains the ship dot at ; the end of the stick LDA Y1 ; Set A = Y1 mod 8 AND #7 ; ; This gives us the height of the top part of the stick, ; as there are eight pixels in each sprite making up the ; stick, so this is the remainder after sprites Y and ; Y+1 are taken away CLC ; Set the pattern number for sprite Y+2 to 219 + A ADC #219 ; STA pattSprite2,Y ; Sprites 219 to 226 contain ship dots with trailing ; sticks, starting with the dot at the bottom of the ; pattern (in pattern 219) up to the dot at the top of ; the pattern (in pattern 226), so this sets the tile ; pattern for sprite Y+2 to the dot height given in A, ; which is the correct pattern for the top of the ship's ; stick LDA attrSprite0,Y ; Set bits 5 to 7 of the attributes for sprite Y to ORA #%11100000 ; ensure that the following attributes are set: STA attrSprite2,Y ; ; * Bit 5 set = show behind background ; * Bit 6 set = flip horizontally ; * Bit 7 set = flip vertically ; ; This makes sure we display the sprite behind the ; 3D ellipse, as the ship is below the ellipse in space, ; and that the dot end of the stick is at the bottom of ; the sprite, not the top ; ; Note that we do this by copying the attributes we set ; up for sprite Y into sprite Y+1, clearing the bits as ; we do so LDA SC2 ; Set the x-coordinate of sprite Y+2 to SC2 STA xSprite2,Y LDA SC2+1 ; Set the y-coordinate of sprite Y+2 to SC2+1, which by STA ySprite2,Y ; now contains the y-coordinate of the top of the stick, RTS ; Return from the subroutine
Name: HideShip [Show more] Type: Subroutine Category: Dashboard Summary: Update the current ship so it is no longer shown on the scanner
Context: See this subroutine on its own page References: This subroutine is called as follows: * LL9 (Part 1 of 12) calls HideShip
.HideShip 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 [Show more] Type: Subroutine Category: Dashboard Summary: Hide the current ship from the scanner
Context: See this subroutine on its own page References: This subroutine is called as follows: * HideFromScanner_b1 calls HideFromScanner
.HideFromScanner 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 INWK+33 ; Set X to the number of the current ship on the ; scanner, which is in ship data byte #33 BEQ hide2 ; If byte #33 for the current ship is zero, then the ; ship doesn't appear on the scanner, so jump to hide2 ; to return from the subroutine as there is nothing to ; hide LDA #0 ; Otherwise we need to hide this ship, so we start by STA scannerNumber,X ; zeroing the scannerNumber entry for ship number X, so ; it no longer has an allocated scanner number ; We now hide the three sprites used to show this ship ; on the scanner ; ; There are four data bytes for each sprite in the ; sprite buffer, and there are three sprites used to ; display each ship on the scanner, so we start by ; calculating the offset of the sprite data for this ; ship's scanner sprites TXA ; Set X = (X * 2 + X) * 4 ASL A ; = (3 * X) * 4 ADC INWK+33 ; ASL A ; So X is the index of the sprite buffer data for the ASL A ; three sprites for ship number X on the scanner TAX LDA QQ11 ; If this is not the space view, jump to hide1 as the BNE hide1 ; dashboard is only shown in the space view LDA #240 ; Set A to the y-coordinate that's just below the bottom ; of the screen, so we can hide the required sprites by ; moving them off-screen STA ySprite11,X ; Hide the three scanner sprites for ship number X, so STA ySprite12,X ; the current ship is no longer shown on the scanner STA ySprite13,X ; (the first ship on the scanner, ship number 1, uses ; the three sprites at 14, 15 and 16 in the buffer, and ; each sprite has four bytes in the buffer, so we can ; get the sprite numbers by adding X, which contains the ; offset within the sprite buffer, to the addresses of ; sprites 11, 12 and 13) .hide1 LDA #0 ; Zero the current ship's byte #33 in INWK, so that it STA INWK+33 ; no longer has a ship number on the scanner (a non-zero ; byte #33 represents the ship's number on the scanner, ; but a ship number of zero indicates that the ship is ; not shown on the scanner) .hide2 RTS ; Return from the subroutine
Name: DrawExplosionBurst [Show more] Type: Subroutine Category: Drawing ships Summary: Draw an exploding ship along with an explosion burst made up of colourful sprites
Context: See this subroutine on its own page References: This subroutine is called as follows: * DOEXP calls DrawExplosionBurst
.DrawExplosionBurst LDY #0 ; Set burstSpriteIndex = 0 to use as an index into the STY burstSpriteIndex ; sprite buffer when drawing the four explosion sprites ; below LDA cloudSize ; Fetch the cloud size that we stored above, and store STA Q ; it in Q LDA INWK+34 ; Fetch byte #34 of the ship data block, which contains ; the cloud counter BPL P%+4 ; If the cloud counter < 128, then we are in the first ; half of the cloud's existence, so skip the next ; instruction EOR #$FF ; Flip the value of A so that in the second half of the ; cloud's existence, A counts down instead of up LSR A ; Divide A by 16 so that is has a maximum value of 7 LSR A LSR A LSR A ORA #1 ; Make sure A is at least 1 and store it in U, to STA U ; give us the number of particles in the explosion for ; each vertex LDY #7 ; Fetch byte #7 of the ship blueprint, which contains LDA (XX0),Y ; the explosion count for this ship (i.e. the number of STA TGT ; vertices used as origins for explosion clouds) and ; store it in TGT LDA RAND+1 ; Fetch the current random number seed in RAND+1 and PHA ; store it on the stack, so we can re-randomise the ; seeds when we are done LDY #6 ; Set Y = 6 to point to the byte before the first vertex ; coordinate we stored on the XX3 heap above (we ; increment it below so it points to the first vertex) .burs1 LDX #3 ; We are about to fetch a pair of coordinates from the ; XX3 heap, so set a counter in X for 4 bytes .burs2 INY ; Increment the index in Y so it points to the next byte ; from the coordinate we are copying LDA XX3-7,Y ; Copy byte Y-7 from the XX3 heap to the X-th byte of K3 STA K3,X DEX ; Decrement the loop counter BPL burs2 ; Keep copying vertex coordinates into K3 until we have ; copied all six coordinates ; The above loop copies the vertex coordinates from the ; XX3 heap to K3, reversing them as we go, so it sets ; the following: ; ; K3+3 = x_lo ; K3+2 = x_hi ; K3+1 = y_lo ; K3+0 = y_hi STY CNT ; Set CNT to the index that points to the next vertex on ; the XX3 heap ; We now draw the explosion burst, which consists of ; four colourful sprites that appear for the first part ; of the explosion only ; ; We use sprites 59 to 62 for the explosion burst LDA burstSpriteIndex ; Set burstSpriteIndex = burstSpriteIndex + 4 CLC ; ADC #4 ; So it points to the next sprite in the sprite buffer ; (as each sprite takes up four bytes) CMP #16 ; If burstSpriteIndex >= 16 then we have already BCS burs5 ; processed all four sprites, so jump to burs5 to move ; on to drawing the explosion cloud STA burstSpriteIndex ; Update burstSpriteIndex to the new value TAY ; Set Y to the burst sprite index so we can use it as an ; index into the sprite buffer LDA K3 ; If either of y_hi or x_hi are non-zero, jump to burs3 ORA K3+2 ; to hide this explosion sprite, as the explosion is off BNE burs3 ; the sides of the screen LDA K3+3 ; Set A = x_lo - 4 SBC #3 ; ; As each explosion burst sprite is eight pixels wide, ; this calculates the x-coordinate of the centre of the ; sprite ; ; The SBC #3 actually subtracts 4 as we know the C flag ; is clear, as we passed through a BCS above BCC burs3 ; If the subtraction underflowed then the centre of the ; sprite is off the top of the screen, so jump to burs3 ; to hide this explosion sprite STA xSprite58,Y ; Set the x-coordinate for the explosion burst sprite ; to A (starting from sprite 59, as Y is a minimum of 4) LDA #%00000010 ; Set the attributes for the sprite as follows: STA attrSprite58,Y ; ; * Bits 0-1 = sprite palette 2 ; * Bit 5 clear = show in front of background ; * Bit 6 clear = do not flip horizontally ; * Bit 7 clear = do not flip vertically LDA K3+1 ; Set A = y_lo CMP #128 ; If A < 128 then the sprite is within the space view, BCC burs4 ; so jump to burs4 to configure the rest of the sprite ; Otherwise the sprite is not in the space view, so fall ; through into burs3 to hide this explosion sprite .burs3 LDA #240 ; Hide this explosion burst sprite by setting its STA ySprite58,Y ; y-coordinate to 240, which is off the bottom of the ; screen BNE burs5 ; Jump to burs5 to move on to drawing the explosion ; cloud (this BNE is effectively a JMP as A is never ; zero) .burs4 ADC #10+YPAL ; Set the pixel y-coordinate of the explosion sprite to STA ySprite58,Y ; A + 10 LDA #245 ; Set the sprite's pattern number to 245, which is a STA pattSprite58,Y ; fairly messy explosion pattern .burs5 ; This next part copies bytes #37 to #40 from the ship ; data block into the four random number seeds in RAND ; to RAND+3, EOR'ing them with the vertex index so they ; are different for every vertex. This enables us to ; generate random numbers for drawing each vertex that ; are random but repeatable, which we need when we ; redraw the cloud to remove it ; ; We set the values of bytes #37 to #40 randomly in the ; LL9 routine before calling DOEXP, so the explosion ; cloud is random but repeatable LDY #37 ; Set Y to act as an index into the ship data block for ; byte #37 LDA (INF),Y ; Set the seed at RAND to byte #37, EOR'd with the EOR CNT ; vertex index, so the seeds are different for each STA RAND ; vertex INY ; Increment Y to point to byte #38 LDA (INF),Y ; Set the seed at RAND+1 to byte #38, EOR'd with the EOR CNT ; vertex index, so the seeds are different for each STA RAND+1 ; vertex INY ; Increment Y to point to byte #39 LDA (INF),Y ; Set the seed at RAND+2 to byte #39, EOR'd with the EOR CNT ; vertex index, so the seeds are different for each STA RAND+2 ; vertex INY ; Increment Y to point to byte #40 LDA (INF),Y ; Set the seed at RAND+3 to byte #49, EOR'd with the EOR CNT ; vertex index, so the seeds are different for each STA RAND+3 ; vertex LDY U ; Set Y to the number of particles in the explosion for ; each vertex, which we stored in U above. We will now ; use this as a loop counter to iterate through all the ; particles in the explosion .burs6 SETUP_PPU_FOR_ICON_BAR ; If the PPU has started drawing the icon bar, configure ; the PPU to use nametable 0 and pattern table 0 JSR DORND2 ; Set ZZ to a random number, making sure the C flag STA ZZ ; doesn't affect the outcome LDA K3+1 ; Set (A R) = (y_hi y_lo) STA R ; = y LDA K3 JSR EXS1 ; Set (A X) = (A R) +/- random * cloud size ; = y +/- random * cloud size BNE burs8 ; If A is non-zero, the particle is off-screen as the ; coordinate is bigger than 255), so jump to burs8 to do ; the next particle CPX Yx2M1 ; If X > the y-coordinate of the bottom of the screen BCS burs8 ; (which is in Yx2M1) then the particle is off the ; bottom of the screen, so jump to burs8 to do the next ; particle ; Otherwise X contains a random y-coordinate within the ; cloud STX Y1 ; Set Y1 = our random y-coordinate within the cloud LDA K3+3 ; Set (A R) = (x_hi x_lo) STA R LDA K3+2 JSR EXS1 ; Set (A X) = (A R) +/- random * cloud size ; = x +/- random * cloud size BNE burs7 ; If A is non-zero, the particle is off-screen as the ; coordinate is bigger than 255), so jump to burs8 to do ; the next particle ; Otherwise X contains a random x-coordinate within the ; cloud LDA Y1 ; Set A = our random y-coordinate within the cloud JSR PIXEL ; Draw a point at screen coordinate (X, A) with the ; point size determined by the distance in ZZ .burs7 DEY ; Decrement the loop counter for the next particle BPL burs6 ; Loop back to burs6 until we have done all the ; particles in the cloud LDY CNT ; Set Y to the index that points to the next vertex on ; the XX3 heap CPY TGT ; If Y < TGT, which we set to the explosion count for BCS P%+5 ; this ship (i.e. the number of vertices used as origins JMP burs1 ; for explosion clouds), loop back to burs1 to do a ; cloud for the next vertex PLA ; Restore the current random number seed to RAND+1 that STA RAND+1 ; we stored at the start of the routine LDA K%+6 ; Store the z_lo coordinate for the planet (which will STA RAND+3 ; be pretty random) in the RAND+3 seed RTS ; Return from the subroutine .burs8 JSR DORND2 ; Set A and X to random numbers, making sure the C flag ; doesn't affect the outcome JMP burs7 ; Jump up to burs7 to move on to the next particle
Name: PIXEL2 [Show more] Type: Subroutine Category: Drawing pixels Summary: Draw a stardust particle relative to the screen centre Deep dive: Sprite usage in NES Elite
Context: See this subroutine on its own page References: This subroutine is called as follows: * STARS1 calls PIXEL2 * STARS2 calls PIXEL2 * STARS6 calls PIXEL2

Draw a stardust particle sprite at point (X1, Y1) from the middle of the screen with a size determined by a distance value.
Arguments: X1 The x-coordinate offset Y1 The y-coordinate offset (positive means up the screen from the centre, negative means down the screen) ZZ The distance of the point (further away = smaller point) Y The number of the stardust particle (1 to 20)
.PIXEL2 STY T1 ; Store Y in T1 so we can retrieve it at the end of the ; subroutine TYA ; Set Y = Y * 4 ASL A ; ASL A ; So Y can be used as an index into the sprite buffer, TAY ; starting with sprite 38 for stardust particle 1, up to ; sprite 57 for stardust particle 20 LDA #210 ; Set A = 210 to use as the pattern number for the ; largest particle of stardust (the stardust particle ; patterns run from pattern 210 to 214, decreasing in ; size as the number increases) LDX ZZ ; If ZZ >= 24, increment A CPX #24 ADC #0 CPX #48 ; If ZZ >= 48, increment A ADC #0 CPX #112 ; If ZZ >= 112, increment A ADC #0 CPX #144 ; If ZZ >= 144, increment A ADC #0 ; So by this point A is 210 for the closest stardust, ; then 211, 212, 213 or 214 for smaller and smaller ; particles as they move further away ; The C flag is clear at this point, which affects the ; SBC #3 below STA pattSprite37,Y ; By this point A is the correct pattern number for the ; distance of the stardust particle, so set the tile ; pattern number for sprite 37 + Y to this pattern LDA X1 ; Fetch the x-coordinate offset into A BPL PX21 ; If the x-coordinate offset is positive, jump to PX21 ; to skip the following negation EOR #%01111111 ; The x-coordinate offset is negative, so flip all the CLC ; bits apart from the sign bit and add 1, to convert it ADC #1 ; from a sign-magnitude number to a signed number .PX21 EOR #%10000000 ; Set A = X1 + 128 - 4 SBC #3 ; ; So X is now the offset converted to an x-coordinate, ; centred on x-coordinate 128, less a margin of 4 ; ; We know that the C flag is clear at this point, so the ; SBC #3 actually subtracts 4 CMP #244 ; If A >= 244 then the stardust particle is off-screen, BCS stpx1 ; so jump to stpx1 to hide the particle's sprite and ; return from the subroutine STA xSprite37,Y ; Set the stardust particle's sprite x-coordinate to A LDA Y1 ; Fetch the y-coordinate offset into A and clear the AND #%01111111 ; sign bit, so A = |Y1| CMP halfScreenHeight ; If A >= halfScreenHeight then the stardust particle BCS stpx1 ; is off the screen, so jump to stpx1 to hide the ; particle's sprite and return from the subroutine LDA Y1 ; Fetch the y-coordinate offset into A BPL PX22 ; If the y-coordinate offset is positive, jump to PX22 ; to skip the following negation EOR #%01111111 ; The y-coordinate offset is negative, so flip all the ADC #1 ; bits apart from the sign bit and add 1, to convert it ; from a sign-magnitude number to a signed number .PX22 STA T ; Set A = halfScreenHeight - Y1 + 10 LDA halfScreenHeight ; SBC T ; So if Y1 is positive we display the point up from the ADC #10+YPAL ; centre at y-coordinate halfScreenHeight, while a ; negative Y1 means down from the centre STA ySprite37,Y ; Set the stardust particle's sprite y-coordinate to A LDY T1 ; Restore the value of Y from T1 so it is preserved RTS ; Return from the subroutine .stpx1 ; If we get here then we do not want to show the ; stardust particle on-screen LDA #240 ; Hide the stardust particle's sprite by setting its STA ySprite37,Y ; y-coordinate to 240, which is off the bottom of the ; screen LDY T1 ; Restore the value of Y from T1 so it is preserved RTS ; Return from the subroutine
Name: Vectors_b1 [Show more] Type: Variable Category: Utility routines Summary: Vectors and padding at the end of ROM bank 1 Deep dive: Splitting NES Elite across multiple ROM banks
Context: See this variable on its own page References: No direct references to this variable in this source file
FOR I%, P%, $BFF9 EQUB $FF ; Pad out the rest of the ROM bank with $FF NEXT IF _NTSC EQUW Interrupts_b1+$4000 ; Vector to the NMI handler in case this bank is ; loaded into $C000 during start-up (the handler ; contains an RTI so the interrupt is processed but ; has no effect) EQUW ResetMMC1_b1+$4000 ; Vector to the RESET handler in case this bank is ; loaded into $C000 during start-up (the handler ; resets the MMC1 mapper to map bank 7 into $C000 ; instead) EQUW Interrupts_b1+$4000 ; Vector to the IRQ/BRK handler in case this bank is ; loaded into $C000 during start-up (the handler ; contains an RTI so the interrupt is processed but ; has no effect) ELIF _PAL EQUW NMI ; Vector to the NMI handler EQUW ResetMMC1_b1+$4000 ; Vector to the RESET handler in case this bank is ; loaded into $C000 during start-up (the handler ; resets the MMC1 mapper to map bank 7 into $C000 ; instead) EQUW IRQ ; Vector to the IRQ/BRK handler ENDIF
Save bank1.bin
PRINT "S.bank1.bin ", ~CODE%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD% SAVE "3-assembled-output/bank1.bin", CODE%, P%, LOAD%