Skip to navigation

BBC Micro Elite

Elite C source

ELITE C FILE Produces the binary file ELTC.bin that gets loaded by elite-bcfs.asm.
CODE_C% = P% LOAD_C% = LOAD% +P% - CODE%
Name: TACTICS (Part 1 of 7) [View individually] Type: Subroutine Category: Tactics Summary: Apply tactics: Process missiles, both enemy missiles and our own
This section implements missile tactics and is entered at TA18 from the main entry point below, if the current ship is a missile. Specifically: * If E.C.M. is active, destroy the missile * If the missile is hostile towards us, then check how close it is. If it hasn't reached us, jump to part 3 so it can streak towards us, otherwise we've been hit, so process a large amount of damage to our ship * Otherwise see how close the missile is to its target. If it has not yet reached its target, give the target a chance to activate its E.C.M. if it has one, otherwise jump to TA19 with K3 set to the vector from the target to the missile * If it has reached its target and the target is the space station, destroy the missile, potentially damaging us if we are nearby * If it has reached its target and the target is a ship, destroy the missile and the ship, potentially damaging us if we are nearby
{ .TA34 \ If we get here, the missile is hostile LDA #0 \ Set A to x_hi OR y_hi OR z_hi JSR MAS4 BEQ P%+5 \ If A = 0 then the missile is very close to our ship, \ so skip the following instruction JMP TA21 \ Jump down to part 3 to set up the vectors and skip \ straight to aggressive manoeuvring JSR TA87+3 \ The missile has hit our ship, so call TA87+3 to set \ bit 7 of the missile's byte #31, which marks the \ missile as being killed JSR EXNO3 \ Make the sound of the missile exploding LDA #250 \ Call OOPS to damage the ship by 250, which is a pretty JMP OOPS \ big hit, and return from the subroutine using a tail \ call .TA18 \ This is the entry point for missile tactics and is \ called from the main TACTICS routine below LDA ECMA \ If an E.C.M. is currently active (either our's or an BNE TA35 \ opponent's), jump to TA35 to destroy this missile LDA INWK+32 \ Fetch the AI flag from byte #32 and if bit 6 is set ASL A \ (i.e. missile is hostile), jump up to TA34 to check BMI TA34 \ whether the missile has hit us LSR A \ Otherwise shift A right again. We know bits 6 and 7 \ are now clear, so this leaves bits 0-5. Bits 1-5 \ contain the target's slot number, and bit 0 is cleared \ in FRMIS when a missile is launched, so A contains \ the slot number shifted left by 1 (i.e. doubled) so we \ can use it as an index for the two-byte address table \ at UNIV TAX \ Copy the address of the target ship's data block from LDA UNIV,X \ UNIV(X+1 X) to V(1 0) STA V LDA UNIV+1,X STA V+1 LDY #2 \ K3(2 1 0) = (x_sign x_hi x_lo) - x-coordinate of JSR TAS1 \ target ship LDY #5 \ K3(5 4 3) = (y_sign y_hi z_lo) - y-coordinate of JSR TAS1 \ target ship LDY #8 \ K3(8 7 6) = (z_sign z_hi z_lo) - z-coordinate of JSR TAS1 \ target ship \ So K3 now contains the vector from the target ship to \ the missile LDA K3+2 \ Set A = OR of all the sign and high bytes of the ORA K3+5 \ above, clearing bit 7 (i.e. ignore the signs) ORA K3+8 AND #%01111111 ORA K3+1 ORA K3+4 ORA K3+7 BNE TA64 \ If the result is non-zero, then the missile is some \ distance from the target, so jump down to TA64 see if \ the target activates its E.C.M. LDA INWK+32 \ Fetch the AI flag from byte #32 and if only bits 7 and CMP #%10000010 \ 1 are set (AI is enabled and the target is slot 1, the BEQ TA35 \ space station), jump to TA35 to destroy this missile, \ as the space station ain't kidding around LDY #31 \ Fetch byte #31 (the exploding flag) of the target ship LDA (V),Y \ into A BIT M32+1 \ M32 contains an LDY #32 instruction, so M32+1 contains \ 32, so this instruction tests A with %00100000, which \ checks bit 5 of A (the "already exploding?" bit) BNE TA35 \ If the target ship is already exploding, jump to TA35 \ to destroy this missile ORA #%10000000 \ Otherwise set bit 7 of the target's byte #31 to mark STA (V),Y \ the ship as having been killed, so it explodes .TA35 LDA INWK \ Set A = x_lo OR y_lo OR z_lo of the missile ORA INWK+3 ORA INWK+6 BNE TA87 \ If A is non-zero then the missile is not near our \ ship, so jump to TA87 to skip damaging our ship LDA #80 \ Otherwise the missile just got destroyed near us, so JSR OOPS \ call OOPS to damage the ship by 80, which is nowhere \ near as bad as the 250 damage from a missile slamming \ straight into us, but it's still pretty nasty .TA87 JSR EXNO2 \ Call EXNO2 to process the fact that we have killed a \ missile (so increase the kill tally, make an explosion \ sound and so on) ASL INWK+31 \ Set bit 7 of the missile's byte #31 flag to mark it as SEC \ having been killed, so it explodes ROR INWK+31 .TA1 RTS \ Return from the subroutine .TA64 \ If we get here then the missile has not reached the \ target JSR DORND \ Set A and X to random numbers CMP #16 \ If A >= 16 (94% chance), jump down to TA19 with the BCS TA19 \ vector from the target to the missile in K3 .M32 LDY #32 \ Fetch byte #32 for the target and shift bit 0 (E.C.M.) LDA (V),Y \ into the C flag LSR A BCC TA19 \ If the C flag is clear then the target does not have \ E.C.M. fitted, so jump down to TA19 with the vector \ from the target to the missile in K3 JMP ECBLB2 \ The target has E.C.M., so jump to ECBLB2 to set it \ off, returning from the subroutine using a tail call
Name: TACTICS (Part 2 of 7) [View individually] Type: Subroutine Category: Tactics Summary: Apply tactics: Escape pod, station, lone Thargon, safe-zone pirate
This section contains the main entry point at TACTICS, which is called from part 2 of MVEIT for ships that have the AI flag set (i.e. bit 7 of byte #32). This part does the following: * If this is a missile, jump up to the missile code in part 1 * If this is an escape pod, point it at the planet and jump to the manoeuvring code in part 7 * If this is the space station and it is hostile, spawn a cop and we're done * If this is a lone Thargon without a mothership, set it adrift aimlessly and we're done * If this is a pirate and we are within the space station safe zone, stop the pirate from attacking * Recharge the ship's energy banks by 1 Arguments: X The ship type
.^TACTICS CPX #MSL \ If this is a missile, jump up to TA18 to implement BEQ TA18 \ missile tactics CPX #ESC \ If this is not an escape pod, skip the following two BNE P%+8 \ instructions JSR SPS1 \ This is an escape pod, so call SPS1 to calculate the \ vector to the planet and store it in XX15 JMP TA15 \ Jump down to TA15 CPX #SST \ If this is not the space station, jump down to TA13 BNE TA13 JSR DORND \ This is the space station, so set A and X to random CMP #140 \ numbers and if A < 140 (55% chance) return from the BCC TA14-1 \ subroutine (as TA14-1 contains an RTS) LDA MANY+COPS \ We only call the tactics routine for the space station CMP #4 \ when it is hostile, so first check the number of cops BCS TA14-1 \ in the vicinity, and if we already have 4 or more, we \ don't need to spawn any more, so return from the \ subroutine (as TA14-1 contains an RTS) LDX #COPS \ Call SFS1 to spawn a cop from the space station that LDA #%11110001 \ is hostile, has AI and has E.C.M., and return from the JMP SFS1 \ subroutine using a tail call .TA13 CPX #TGL \ If this is not a Thargon, jump down to TA14 BNE TA14 LDA MANY+THG \ If there is at least one Thargoid in the vicinity, BNE TA14 \ jump down to TA14 LSR INWK+32 \ This is a Thargon but there is no Thargoid mothership, ASL INWK+32 \ so clear bit 0 of the AI flag to disable its E.C.M. LSR INWK+27 \ And halve the Thargon's speed RTS \ Return from the subroutine .TA14 CPX #CYL \ If A >= #CYL, i.e. this is a Cobra Mk III trader (as BCS TA62 \ asteroids and cargo canisters never have AI), jump \ down to TA62 CPX #COPS \ If this is a cop, jump down to TA62 BEQ TA62 LDA SSPR \ If we aren't within range of the space station, jump BEQ TA62 \ down to TA62 LDA INWK+32 \ This is a pirate or bounty hunter, but we are inside AND #%10000001 \ the space station's safe zone, so clear bits 1-6 of STA INWK+32 \ the AI flag to stop it being hostile, because even \ pirates aren't crazy enough to breach the station's \ no-fire zone .TA62 LDY #14 \ If the ship's energy is greater or equal to the LDA INWK+35 \ maximum value from the ship's blueprint pointed to by CMP (XX0),Y \ XX0, then skip the next instruction BCS TA21 INC INWK+35 \ The ship's energy is not at maximum, so recharge the \ energy banks by 1
Name: TACTICS (Part 3 of 7) [View individually] Type: Subroutine Category: Tactics Summary: Apply tactics: Calculate dot product to determine ship's aim
This section sets up some vectors and calculates dot products. Specifically: * Calculate the dot product of the ship's nose vector (i.e. the direction it is pointing) with the vector between us and the ship. This value will help us work out later on whether the enemy ship is pointing towards us, and therefore whether it can hit us with its lasers.
.TA21 LDX #8 \ We now want to copy the ship's x, y and z coordinates \ from INWK to K3, so set up a counter for 9 bytes .TAL1 LDA INWK,X \ Copy the X-th byte from INWK to the X-th byte of K3 STA K3,X DEX \ Decrement the counter BPL TAL1 \ Loop back until we have copied all 9 bytes .TA19 \ If this is a missile that's heading for its target \ (not us, one of the other ships), then the missile \ routine at TA18 above jumps here after setting K3 to \ the vector from the target to the missile JSR TAS2 \ Normalise the vector in K3 and store the normalised \ version in XX15, so XX15 contains the normalised \ vector from our ship to the ship we are applying AI \ tactics to (or the normalised vector from the target \ to the missile - in both cases it's the vector from \ the potential victim to the attacker) LDY #10 \ Set (A X) = nosev . XX15 JSR TAS3 STA CNT \ Store the high byte of the dot product in CNT. The \ bigger the value, the more aligned the two ships are, \ with a maximum magnitude of 36 (96 * 96 >> 8). If CNT \ is positive, the ships are facing in a similar \ direction, if it's negative they are facing in \ opposite directions
Name: TACTICS (Part 4 of 7) [View individually] Type: Subroutine Category: Tactics Summary: Apply tactics: Check energy levels, maybe launch escape pod if low
This section works out what kind of condition the ship is in. Specifically: * Rarely (2.5% chance) roll the ship by a noticeable amount * If the ship has at least half its energy banks full, jump to part 6 to consider firing the lasers * If the ship isn't really low on energy, jump to part 5 to consider firing a missile * Rarely (10% chance) the ship runs out of both energy and luck, and bails, launching an escape pod and drifting in space
LDA TYPE \ If this is not a missile, skip the following CMP #MSL \ instruction BNE P%+5 JMP TA20 \ This is a missile, so jump down to TA20 to get \ straight into some aggressive manoeuvring JSR DORND \ Set A and X to random numbers CMP #250 \ If A < 250 (97.5% chance), jump down to TA7 to skip BCC TA7 \ the following JSR DORND \ Set A and X to random numbers ORA #104 \ Bump A up to at least 104 and store in the roll STA INWK+29 \ counter, to gives the ship a noticeable roll .TA7 LDY #14 \ Set A = the ship's maximum energy / 2 LDA (XX0),Y LSR A CMP INWK+35 \ If the ship's current energy in byte #35 > A, i.e. the BCC TA3 \ ship has at least half of its energy banks charged, \ jump down to TA3 LSR A \ If the ship's current energy in byte #35 > A / 4, i.e. LSR A \ the ship is not into the last 1/8th of its energy, CMP INWK+35 \ jump down to ta3 to consider firing a missile BCC ta3 JSR DORND \ Set A and X to random numbers CMP #230 \ If A < 230 (90% chance), jump down to ta3 to consider BCC ta3 \ firing a missile LDA TYPE \ If this is a Thargoid, jump down to ta3 to consider CMP #THG \ launching a Thargon BEQ ta3 \ By this point, the ship has run out of both energy and \ luck, so it's time to bail LDA #0 \ Set the AI flag to 0 to disable AI, hostility and STA INWK+32 \ E.C.M., so the ship's a sitting duck JMP SESCP \ Jump to SESCP to spawn an escape pod from the ship, \ returning from the subroutine using a tail call
Name: TACTICS (Part 5 of 7) [View individually] Type: Subroutine Category: Tactics Summary: Apply tactics: Consider whether to launch a missile at us
This section considers whether to launch a missile. Specifically: * If the ship doesn't have any missiles, skip to the next part * If an E.C.M. is firing, skip to the next part * Randomly decide whether to fire a missile (or, in the case of Thargoids, release a Thargon), and if we do, we're done
.ta3 \ If we get here then the ship has less than half energy \ so there may not be enough juice for lasers, but let's \ see if we can fire a missile LDA INWK+31 \ Set A = bits 0-2 of byte #31, the number of missiles AND #%00000111 \ the ship has left BEQ TA3 \ If it doesn't have any missiles, jump to TA3 STA T \ Store the number of missiles in T JSR DORND \ Set A and X to random numbers AND #31 \ Restrict A to a random number in the range 0-31 CMP T \ If A >= T, which is quite likely, though less likely BCS TA3 \ with higher numbers of missiles, jump to TA3 LDA ECMA \ If an E.C.M. is currently active (either our's or an BNE TA3 \ opponent's), jump to TA3 DEC INWK+31 \ We're done with the checks, so it's time to fire off a \ missile, so reduce the missile count in byte #31 by 1 LDA TYPE \ If this is not a Thargoid, jump down to TA16 to launch CMP #THG \ a missile BNE TA16 LDX #TGL \ This is a Thargoid, so instead of launching a missile, LDA INWK+32 \ the mothership launches a Thargon, so call SFS1 to JMP SFS1 \ spawn a Thargon from the parent ship, and return from \ the subroutine using a tail call .TA16 JMP SFRMIS \ Jump to SFRMIS to spawn a missile as a child of the \ current ship, make a noise and print a message warning \ of incoming missiles, and return from the subroutine \ using a tail call
Name: TACTICS (Part 6 of 7) [View individually] Type: Subroutine Category: Tactics Summary: Apply tactics: Consider firing a laser at us, if aim is true
This section looks at potentially firing the ship's laser at us. Specifically: * If the ship is not pointing at us, skip to the next part * If the ship is pointing at us but not accurately, fire its laser at us and skip to the next part * If we are in the ship's crosshairs, register some damage to our ship, slow down the attacking ship, make the noise of us being hit by laser fire, and we're done
.TA3 \ If we get here then the ship either has plenty of \ energy, or levels are low but it couldn't manage to \ launch a missile, so maybe we can fire the laser? LDA #0 \ Set A to x_hi OR y_hi OR z_hi JSR MAS4 AND #%11100000 \ If any of the hi bytes have any of bits 5-7 set, then BNE TA4 \ jump to TA4 to skip the laser, as the ship is too far \ away from us to hit us with a laser LDX CNT \ Set X = the dot product set above in CNT. If this is \ positive, this ship and our ship are facing in similar \ directions, but if it's negative then we are facing \ each other, so for us to be in the enemy ship's line \ of fire, X needs to be negative. The value in X can \ have a maximum magnitude of 36, which would mean we \ were facing each other square on, so in the following \ code we check X like this: \ \ X = 0 to -31, we are not in the enemy ship's line \ of fire, so they can't shoot at us \ \ X = -32 to -34, we are in the enemy ship's line \ of fire, so they can shoot at us, but they can't \ hit us as we're not dead in their crosshairs \ \ X = -35 to -36, we are bang in the middle of the \ enemy ship's crosshairs, so they can not only \ shoot us, they can hit us CPX #160 \ If X < 160, i.e. X > -32, then we are not in the enemy BCC TA4 \ ship's line of fire, so jump to TA4 LDA INWK+31 \ Set bit 6 in byte #31 to denote that the ship is ORA #%01000000 \ firing its laser at us STA INWK+31 CPX #163 \ If X < 163, i.e. X > -35, then we are not in the enemy BCC TA4 \ ship's crosshairs, so jump to TA4 .HIT LDY #19 \ We are being hit by enemy laser fire, so fetch the LDA (XX0),Y \ enemy ship's laser power from their ship's blueprint \ into A LSR A \ Halve their laser power to get the amount of damage we \ should take JSR OOPS \ Call OOPS to take some damage, which could do anything \ from reducing the shields and energy, all the way to \ losing cargo or dying (if the latter, we don't come \ back from this subroutine) DEC INWK+28 \ Halve the attacking ship's acceleration in byte #28, LDA ECMA \ If an E.C.M. is currently active (either our's or an BNE TA10 \ opponent's), return from the subroutine without making \ the laser-strike sound (as TA10 contains an RTS) LDA #8 \ Call the NOISE routine with A = 8 to make the sound JMP NOISE \ of us being hit by lasers, returning from the \ subroutine using a tail call
Name: TACTICS (Part 7 of 7) [View individually] Type: Subroutine Category: Tactics Summary: Apply tactics: Set pitch, roll, and acceleration
This section looks at manoeuvring the ship. Specifically: * Work out which direction the ship should be moving, depending on whether it's an escape pod, where it is, which direction it is pointing, and how aggressive it is * Set the pitch and roll counters to head in that direction * Speed up or slow down, depending on where the ship is in relation to us
.TA4 LDA INWK+7 \ If z_hi >= 3 then the ship is quite far away, so jump CMP #3 \ down to TA5 BCS TA5 LDA INWK+1 \ Otherwise set A = x_hi OR y_hi and extract bits 1-7 ORA INWK+4 AND #%11111110 BEQ TA15 \ If A = 0 then the ship is pretty close to us, so jump \ to TA15 so it heads away from us .TA5 \ If we get here then the ship is quite far away JSR DORND \ Set A and X to random numbers ORA #%10000000 \ Set bit 7 of A CMP INWK+32 \ If A >= byte #32 (the ship's AI flag) then jump down BCS TA15 \ to TA15 so it heads away from us \ We get here if A < byte #32, and the chances of this \ being true are greater with high values of byte #32. \ In other words, higher byte #32 values increase the \ chances of a ship changing direction to head towards \ us - or, to put it another way, ships with higher \ byte #32 values are spoiling for a fight. Thargoids \ have byte #32 set to 255, which explains an awful lot .TA20 \ If this is a missile we will have jumped straight \ here, but we also get here if the ship is either far \ away and aggressive, or not too close LDA XX15 \ Reverse the signs of XX15 and the dot product in CNT, EOR #%10000000 \ starting with the x-coordinate STA XX15 LDA XX15+1 \ Then reverse the sign of the y-coordinate EOR #%10000000 STA XX15+1 LDA XX15+2 \ And then the z-coordinate, so now the XX15 vector goes EOR #%10000000 \ from the enemy ship to our ship (it was previously the STA XX15+2 \ other way round) LDA CNT \ And finally change the sign of the dot product in CNT, EOR #%10000000 \ so now it's positive if the ships are facing each STA CNT \ other, and negative if they are facing the same way .TA15 \ If we get here, then one of the following is true: \ \ * This is an escape pod and XX15 is pointing towards \ the planet \ \ * The ship is pretty close to us, or it's just not \ very aggressive (though there is a random factor \ at play here too). XX15 is still pointing from our \ ship towards the enemy ship \ \ * The ship is aggressive (though again, there's an \ element of randomness here). XX15 is pointing from \ the enemy ship towards our ship \ \ * This is a missile heading for a target. XX15 is \ pointing from the missile towards the target \ \ We now want to move the ship in the direction of XX15, \ which will make aggressive ships head towards us, and \ ships that are too close turn away. Escape pods, \ meanwhile, head off towards the planet in search of a \ space station, and missiles home in on their targets LDY #16 \ Set (A X) = roofv . XX15 JSR TAS3 \ \ This will be positive if XX15 is pointing in the same \ direction as an arrow out of the top of the ship, in \ other words if the ship should pull up to head in the \ direction of XX15 EOR #%10000000 \ Set the ship's pitch counter to 3, with the opposite AND #%10000000 \ sign to the dot product result, which gently pitches ORA #%00000011 \ the ship towards the direction of the XX15 vector STA INWK+30 LDA INWK+29 \ Fetch the roll counter from byte #29 into A and clear AND #%01111111 \ the sign bit CMP #16 \ If A >= 16 then jump to TA6, as the ship is already BCS TA6 \ in the process of rolling LDY #22 \ Set (A X) = sidev . XX15 JSR TAS3 \ \ This will be positive if XX15 is pointing in the same \ direction as an arrow out of the right side of the \ ship, in other words if the ship should roll right to \ head in the direction of XX15 EOR INWK+30 \ Set the ship's roll counter to 5, with the sign set to AND #%10000000 \ positive if the pitch counter and dot product have EOR #%10000101 \ different signs, negative if they have the same sign STA INWK+29 .TA6 LDA CNT \ Fetch the dot product, and if it's negative jump to BMI TA9 \ TA9, as the ships are facing away from each other and \ the ship might want to slow down to take another shot CMP #22 \ The dot product is positive, so the ships are facing BCC TA9 \ each other. If A < 22 then the ships are not heading \ directly towards each other, so jump to TA9 to slow \ down LDA #3 \ Otherwise set the acceleration in byte #28 to 3 STA INWK+28 RTS \ Return from the subroutine .TA9 AND #%01111111 \ Clear the sign bit of the dot product in A CMP #18 \ If A < 18 then the ship is way off the XX15 vector, so BCC TA10 \ return from the subroutine (TA10 contains an RTS) \ without slowing down, as it still has quite a bit of \ turning to do to get on course LDA #&FF \ Otherwise set A = -1 LDX TYPE \ If this is not a missile then skip the ASL instruction CPX #MSL BNE P%+3 ASL A \ This is a missile, so set A = -2, as missiles are more \ nimble and can brake more quickly STA INWK+28 \ Ser the ship's acceleration to A .TA10 RTS \ Return from the subroutine }
Name: TAS1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate K3 = (x_sign x_hi x_lo) - V(1 0)
Calculate one of the following, depending on the value in Y: K3(2 1 0) = (x_sign x_hi x_lo) - x-coordinate in V(1 0) K3(5 4 3) = (y_sign y_hi z_lo) - y-coordinate in V(1 0) K3(8 7 6) = (z_sign z_hi z_lo) - z-coordinate in V(1 0) where the first coordinate is from the ship data block in INWK, and the second coordinate is from the ship data block pointed to by V(1 0). Arguments: V(1 0) The address of the ship data block to subtract Y The coordinate in the V(1 0) block to subtract: * If Y = 2, subtract the x-coordinate and store the result in K3(2 1 0) * If Y = 5, subtract the y-coordinate and store the result in K3(5 4 3) * If Y = 8, subtract the z-coordinate and store the result in K3(8 7 6)
.TAS1 { LDA (V),Y \ Copy the sign byte of the V(1 0) coordinate into K+3, EOR #%10000000 \ flipping it in the process STA K+3 DEY \ Copy the high byte of the V(1 0) coordinate into K+2 LDA (V),Y STA K+2 DEY \ Copy the high byte of the V(1 0) coordinate into K+1, LDA (V),Y \ so now: STA K+1 \ \ K(3 2 1) = - coordinate in V(1 0) STY U \ Copy the index (now 0, 3 or 6) into U and X LDX U JSR MVT3 \ Call MVT3 to add the same coordinates, but this time \ from INWK, so this would look like this for the \ x-axis: \ \ K(3 2 1) = (x_sign x_hi x_lo) + K(3 2 1) \ = (x_sign x_hi x_lo) - coordinate in V(1 0) LDY U \ Restore the index into Y, though this instruction has \ no effect, as Y is not used again, either here or \ following calls to this routine STA K3+2,X \ Store K(3 2 1) in K3+X(2 1 0), starting with the sign \ byte LDA K+2 \ And then doing the high byte STA K3+1,X LDA K+1 \ And finally the low byte STA K3,X RTS \ Return from the subroutine }
Name: HITCH [View individually] Type: Subroutine Category: Tactics Summary: Work out if the ship in INWK is in our crosshairs
This is called by the main flight loop to see if we have laser or missile lock on an enemy ship. Returns: C flag Set if the ship is in our crosshairs, clear if it isn't Other entry points: HI1 Contains an RTS
.HITCH { CLC \ Clear the C flag so we can return with it cleared if \ our checks fail LDA INWK+8 \ Set A = z_sign BNE HI1 \ If A is non-zero then the ship is behind us and can't \ be in our crosshairs, so return from the subroutine \ with the C flag clear (as HI1 contains an RTS) LDA TYPE \ If the ship type has bit 7 set then it is the planet BMI HI1 \ or sun, which we can't target or hit with lasers, so \ return from the subroutine with the C flag clear (as \ HI1 contains an RTS) LDA INWK+31 \ Fetch bit 5 of byte #31 (the exploding flag) and OR AND #%00100000 \ with x_hi and y_hi ORA INWK+1 ORA INWK+4 BNE HI1 \ If this value is non-zero then either the ship is \ exploding (so we can't target it), or the ship is too \ far away from our line of fire to be targeted, so \ return from the subroutine with the C flag clear (as \ HI1 contains an RTS) LDA INWK \ Set A = x_lo JSR SQUA2 \ Set (A P) = A * A = x_lo^2 STA S \ Set (S R) = (A P) = x_lo^2 LDA P STA R LDA INWK+3 \ Set A = y_lo JSR SQUA2 \ Set (A P) = A * A = y_lo^2 TAX \ Store the high byte in X LDA P \ Add the two low bytes, so: ADC R \ STA R \ R = P + R TXA \ Restore the high byte into A and add S to give the ADC S \ following: \ \ (A R) = (S R) + (A P) = x_lo^2 + y_lo^2 BCS FR1-2 \ If the addition just overflowed then there is no way \ our crosshairs are within the ship's targetable area, \ so return from the subroutine with the C flag clear \ (as FR1-2 contains a CLC then an RTS) STA S \ Set (S R) = (A P) = x_lo^2 + y_lo^2 LDY #2 \ Fetch the ship's blueprint and set A to the high byte LDA (XX0),Y \ of the targetable area of the ship CMP S \ We now compare the high bytes of the targetable area \ and the calculation in (S R): \ \ * If A >= S then then the C flag will be set \ \ * If A < S then the C flag will be C clear BNE HI1 \ If A <> S we have just set the C flag correctly, so \ return from the subroutine (as HI1 contains an RTS) DEY \ The high bytes were identical, so now we fetch the LDA (XX0),Y \ low byte of the targetable area into A CMP R \ We now compare the low bytes of the targetable area \ and the calculation in (S R): \ \ * If A >= R then the C flag will be set \ \ * If A < R then the C flag will be C clear .^HI1 RTS \ Return from the subroutine }
Name: FRS1 [View individually] Type: Subroutine Category: Tactics Summary: Launch a ship straight ahead of us, below the laser sights
This is used in two places: * When we launch a missile, in which case the missile is the ship that is launched ahead of us * When we launch our escape pod, in which case it's our abandoned Cobra Mk III that is launched ahead of us * The fq1 entry point is used to launch a bunch of cargo canisters ahead of us as part of the death screen Arguments: X The type of ship to launch ahead of us Returns: C flag Set if the ship was successfully launched, clear if it wasn't (as there wasn't enough free memory) Other entry points: fq1 Used to add a cargo canister to the universe
.FRS1 { JSR ZINF \ Call ZINF to reset the INWK ship workspace LDA #28 \ Set y_lo = 28 STA INWK+3 LSR A \ Set z_lo = 14, so the launched ship starts out STA INWK+6 \ ahead of us LDA #%10000000 \ Set y_sign to be negative, so the launched ship is STA INWK+5 \ launched just below our line of sight LDA MSTG \ Set A to the missile lock target, shifted left so the ASL A \ slot number is in bits 1-4 ORA #%10000000 \ Set bit 7 and store the result in byte #32, the AI STA INWK+32 \ flag launched ship for the launched ship. For missiles \ this enables AI (bit 7), makes it friendly towards us \ (bit 6), sets the target to the value of MSTG (bits \ 1-4), and sets its lock status as launched (bit 0). \ It doesn't matter what it does for our abandoned \ Cobra, as the AI flag gets overwritten once we return \ from the subroutine back to the ESCAPE routine that \ called FRS1 in the first place .^fq1 LDA #&60 \ Set byte #14 (nosev_z_hi) to 1 (&60), so the launched STA INWK+14 \ ship is pointing away from us ORA #128 \ Set byte #22 (sidev_x_hi) to -1 (&D0), so the launched STA INWK+22 \ ship has the same orientation as spawned ships, just \ pointing away from us (if we set sidev to +1 instead, \ this ship would be a mirror image of all the other \ ships, which are spawned with -1 in nosev and +1 in \ sidev) LDA DELTA \ Set byte #27 (speed) to 2 * DELTA, so the launched ROL A \ ship flies off at twice our speed STA INWK+27 TXA \ Add a new ship of type X to our local bubble of JMP NWSHP \ universe and return from the subroutine using a tail \ call }
Name: FRMIS [View individually] Type: Subroutine Category: Tactics Summary: Fire a missile from our ship
We fired a missile, so send it streaking away from us to unleash mayhem and destruction on our sworn enemies.
.FRMIS { LDX #MSL \ Call FRS1 to launch a missile straight ahead of us JSR FRS1 BCC FR1 \ If FRS1 returns with the C flag clear, then there \ isn't room in the universe for our missile, so jump \ down to FR1 to display a "missile jammed" message LDX MSTG \ Fetch the slot number of the missile's target JSR GINF \ Get the address of the data block for the target ship \ and store it in INF LDA FRIN,X \ Fetch the ship type of the missile's target into A JSR ANGRY \ Call ANGRY to make the target ship hostile LDY #0 \ We have just launched a missile, so we need to remove JSR ABORT \ missile lock and hide the leftmost indicator on the \ dashboard by setting it to black (Y = 0) DEC NOMSL \ Reduce the number of missiles we have by 1 LDA #48 \ Call the NOISE routine with A = 48 to make the sound JMP NOISE \ of a missile launch, returning from the subroutine \ using a tail call }
Name: ANGRY [View individually] Type: Subroutine Category: Tactics Summary: Make a ship hostile
All this actually does is set the ship's hostile flag, start it turning and give it a kick of acceleration - later calls to TACTICS will make the ship actually attack. Arguments: A The type of ship to irritate INF The address of the data block for the ship to infuriate
.ANGRY { CMP #SST \ If this is the space station, jump to AN2 to make the BEQ AN2 \ space station hostile BCS HI1 \ If A >= #SST then this is a missile, asteroid, cargo \ canister, Thargon or escape pod, and they can't get \ hostile, so return from the subroutine (as HI1 \ contains an RTS) CMP #CYL \ If this is not a Cobra Mk III trader, skip the BNE P%+5 \ following instruction JSR AN2 \ Call AN2 to make the space station hostile LDY #32 \ Fetch the ship's byte #32 (AI flag) LDA (INF),Y BEQ HI1 \ If the AI flag is zero then this ship has no AI and \ it can't get hostile, so return from the subroutine \ (as HI1 contains an RTS) ORA #%10000000 \ Otherwise set bit 7 to ensure AI is definitely enabled STA (INF),Y LDY #28 \ Set the ship's byte #28 (acceleration) to 2, so it LDA #2 \ speeds up STA (INF),Y ASL A \ Set the ship's byte #30 (pitch counter) to 4, so it LDY #30 \ starts pitching STA (INF),Y RTS \ Return from the subroutine .AN2 ASL K%+NI%+32 \ Fetch the AI counter (byte #32) of the second ship SEC \ in the ship data workspace at K%, which is reserved ROR K%+NI%+32 \ for the sun or the space station (in this case it's \ the latter), and set bit 7 to make it hostile CLC \ Clear the C flag, which isn't used by calls to this \ routine, but it does set up the entry point FR1-2 \ so that it clears the C flag and does an RTS RTS \ Return from the subroutine }
Name: FR1 [View individually] Type: Subroutine Category: Tactics Summary: Display the "missile jammed" message
This is shown if there isn't room in the local bubble of universe for a new missile. Other entry points: FR1-2 Clear the C flag and return from the subroutine
.FR1 { LDA #201 \ Print recursive token 41 ("MISSILE JAMMED") as an JMP MESS \ in-flight message and return from the subroutine using \ a tail call }
Name: SESCP [View individually] Type: Subroutine Category: Flight Summary: Spawn an escape pod from the current (parent) ship
This is called when an enemy ship has run out of both energy and luck, so it's time to bail. Other entry points: SFS1-2 Add a missile to the local bubble that has AI enabled, is hostile, but has no E.C.M.
.SESCP { LDX #ESC \ Set X to the ship type for an escape pod LDA #%11111110 \ Set A to an AI flag that has AI enabled, is hostile, \ but has no E.C.M. \ Fall through into SFS1 to spawn the escape pod }
Name: SFS1 [View individually] Type: Subroutine Category: Universe Summary: Spawn a child ship from the current (parent) ship
If the parent is a space station then the child ship is spawned coming out of the slot, and if the child is a cargo canister, it is sent tumbling through space. Otherwise the child ship is spawned with the same ship data as the parent, just with damping disabled and the ship type and AI flag that are passed in A and X. Arguments: A AI flag for the new ship (see the documentation on ship data byte #32 for details) X The ship type of the child to spawn INF Address of the parent's ship data block TYPE The type of the parent ship Returns: C flag Set if ship successfully added, clear if it failed INF INF is preserved XX0 XX0 is preserved INWK The whole INWK workspace is preserved
.SFS1 { STA T1 \ Store the child ship's AI flag in T1 \ Before spawning our child ship, we need to save the \ INF and XX00 variables and the whole INWK workspace, \ so we can restore them later when returning from the \ subroutine LDA XX0 \ Store XX0(1 0) on the stack, so we can restore it PHA \ later when returning from the subroutine LDA XX0+1 PHA LDA INF \ Store INF(1 0) on the stack, so we can restore it PHA \ later when returning from the subroutine LDA INF+1 PHA LDY #NI%-1 \ Now we want to store the current INWK data block in \ temporary memory so we can restore it when we are \ done, and we also want to copy the parent's ship data \ into INWK, which we can do at the same time, so set up \ a counter in Y for NI% bytes .FRL2 LDA INWK,Y \ Copy the Y-th byte of INWK to the Y-th byte of STA XX3,Y \ temporary memory in XX3, so we can restore it later \ when returning from the subroutine LDA (INF),Y \ Copy the Y-th byte of the parent ship's data block to STA INWK,Y \ the Y-th byte of INWK DEY \ Decrement the loop counter BPL FRL2 \ Loop back to copy the next byte until we have done \ them all \ INWK now contains the ship data for the parent ship, \ so now we need to tweak the data before creating the \ new child ship (in this way, the child inherits things \ like location from the parent) LDA TYPE \ Fetch the ship type of the parent into A CMP #SST \ If the parent is not a space station, jump to rx to BNE rx \ skip the following \ The parent is a space station, so the child needs to \ launch out of the space station's slot. The space \ station's nosev vector points out of the station's \ slot, so we want to move the ship along this vector. \ We do this by taking the unit vector in nosev and \ doubling it, so we spawn our ship 2 units along the \ vector from the space station's centre TXA \ Store the child's ship type in X on the stack PHA LDA #32 \ Set the child's byte #27 (speed) to 32 STA INWK+27 LDX #0 \ Add 2 * nosev_x_hi to (x_lo, x_hi, x_sign) to get the LDA INWK+10 \ child's x-coordinate JSR SFS2 LDX #3 \ Add 2 * nosev_y_hi to (y_lo, y_hi, y_sign) to get the LDA INWK+12 \ child's y-coordinate JSR SFS2 LDX #6 \ Add 2 * nosev_z_hi to (z_lo, z_hi, z_sign) to get the LDA INWK+14 \ child's z-coordinate JSR SFS2 PLA \ Restore the child's ship type from the stack into X TAX .rx LDA T1 \ Restore the child ship's AI flag from T1 and store it STA INWK+32 \ in the child's byte #32 (AI) LSR INWK+29 \ Clear bit 0 of the child's byte #29 (roll counter) so ASL INWK+29 \ that its roll dampens (so if we are spawning from a \ space station, for example, the spawned ship won't \ keep rolling forever) TXA \ If the child we are spawning is not a cargo canister, CMP #OIL \ jump to NOIL to skip us setting up the pitch and roll BNE NOIL \ for the canister JSR DORND \ Set A and X to random numbers ASL A \ Set the child's byte #30 (pitch counter) to a random STA INWK+30 \ value, and at the same time set the C flag randomly TXA \ Set the child's byte #27 (speed) to a random value AND #%00001111 \ between 0 and 15 STA INWK+27 LDA #&FF \ Set the child's byte #29 (roll counter) to a full ROR A \ roll, so the canister tumbles through space, with STA INWK+29 \ damping randomly enabled or disabled, depending on the \ C flag from above LDA #OIL \ Set A to the ship type of a cargo canister .NOIL JSR NWSHP \ Add a new ship of type A to the local bubble \ We have now created our child ship, so we need to \ restore all the variables we saved at the start of \ the routine, so they are preserved when we return \ from the subroutine PLA \ Restore INF(1 0) from the stack STA INF+1 PLA STA INF LDX #NI%-1 \ Now to restore the INWK workspace that we saved into \ XX3 above, so set a counter in X for NI% bytes .FRL3 LDA XX3,X \ Copy the Y-th byte of XX3 to the Y-th byte of INWK STA INWK,X DEX \ Decrement the loop counter BPL FRL3 \ Loop back to copy the next byte until we have done \ them all PLA \ Restore XX0(1 0) from the stack STA XX0+1 PLA STA XX0 RTS \ Return from the subroutine }
Name: SFS2 [View individually] Type: Subroutine Category: Moving Summary: Move a ship in space along one of the coordinate axes
Move a ship's coordinates by a certain amount in the direction of one of the axes, where X determines the axis. Mathematically speaking, this routine translates the ship along a single axis by a signed delta. Arguments: A The amount of movement, i.e. the signed delta X Determines which coordinate axis of INWK to move: * X = 0 moves the ship along the x-axis * X = 3 moves the ship along the y-axis * X = 6 moves the ship along the z-axis
.SFS2 { ASL A \ Set R = |A * 2|, with the C flag set to bit 7 of A STA R LDA #0 \ Set bit 7 of A to the C flag, i.e. the sign bit from ROR A \ the original argument in A JMP MVT1 \ Add the delta R with sign A to (x_lo, x_hi, x_sign) \ (or y or z, depending on the value in X) and return \ from the subroutine using a tail call }
Name: LL164 [View individually] Type: Subroutine Category: Drawing circles Summary: Make the hyperspace sound and draw the hyperspace tunnel
See the IRQ1 routine for details on the multi-coloured effect that's used.
.LL164 { LDA #56 \ Call the NOISE routine with A = 56 to make the sound JSR NOISE \ of the hyperspace drive being engaged LDA #1 \ Set HFX to 1, which switches the screen mode to a full STA HFX \ mode 5 screen, therefore making the hyperspace rings \ multi-coloured and all zig-zaggy (see the IRQ1 routine \ for details) LDA #4 \ Set the step size for the hyperspace rings to 4, so \ there are more sections in the rings and they are \ quite round (compared to the step size of 8 used in \ the much more polygonal launch rings) JSR HFS2 \ Call HFS2 to draw the hyperspace tunnel rings DEC HFX \ Set HFX back to 0, so we switch back to the normal \ split-screen mode RTS \ Return from the subroutine }
Name: LAUN [View individually] Type: Subroutine Category: Drawing circles Summary: Make the launch sound and draw the launch tunnel
This is shown when launching from or docking with the space station.
.LAUN { LDA #48 \ Call the NOISE routine with A = 48 to make the sound JSR NOISE \ of the ship launching from the station LDA #8 \ Set the step size for the hyperspace rings to 8, so \ there are fewer sections in the rings and they are \ quite polygonal (compared to the step size of 4 used \ in the much rounder launch rings) \ Fall through into HFS2 to draw the launch tunnel rings }
Name: HFS2 [View individually] Type: Subroutine Category: Drawing circles Summary: Draw the launch or hyperspace tunnel
The animation gets drawn like this. First, we draw a circle of radius 8 at the centre, and then double the radius, draw another circle, double the radius again and draw a circle, and we keep doing this until the radius is bigger than160 (which goes beyond the edge of the screen, which is 256 pixels wide, equivalent to a radius of 128). We then repeat this whole process for an initial circle of radius 9, then radius 10, all the way up to radius 15. This has the effect of making the tunnel appear to be racing towards us as we hurtle out into hyperspace or through the space station's docking tunnel. The hyperspace effect is done in a full mode 5 screen, which makes the rings all coloured and zig-zaggy, while the launch screen is in the normal monochrome mode 4 screen. Arguments: A The step size of the straight lines making up the rings (4 for launch, 8 for hyperspace)
.HFS2 { STA STP \ Store the step size in A JSR TTX66 \ Clear the screen and draw a white border JSR HFS1 \ Call HFS1 below and then fall through into the same \ routine, so this effectively runs HFS1 twice, and as \ HFS1 draws 8 concentric rings, this means we draw 16 \ of them in all .HFS1 LDA #128 \ Set K3 = 128 (the x-coordinate pf the centre of the STA K3 \ screen) LDX #Y \ Set K4 = #Y (the y-coordinate of the centre of the \ screen) STX K4 ASL A \ Set A = 0 STA XX4 \ Set XX4 = 0, which we will use as a counter for \ drawing 8 concentric rings STA K3+1 \ Set the high bytes of K3(1 0) and K4(1 0) to 0 STA K4+1 .HFL5 JSR HFL1 \ Call HFL1 below to draw a set of rings, with each one \ twice the radius of the previous one, until they won't \ fit on-screen INC XX4 \ Increment the counter and fetch it into X LDX XX4 CPX #8 \ If we haven't drawn 8 sets of rings yet, loop back to BNE HFL5 \ HFL5 to draw the next ring RTS \ Return from the subroutine .HFL1 LDA XX4 \ Set K to the ring number in XX4 (0-7) + 8, so K has AND #7 \ a value of 8 to 15, which we will use as the starting CLC \ radius for our next set of rings ADC #8 STA K .HFL2 LDA #1 \ Set LSP = 1 to reset the ball line heap STA LSP JSR CIRCLE2 \ Call CIRCLE2 to draw a circle with the centre at \ (K3(1 0), K4(1 0)) and radius K ASL K \ Double the radius in K BCS HF8 \ If the radius had a 1 in bit 7 before the above shift, \ then doubling K will means the circle will no longer \ fit on the screen (which is width 256), so jump to \ HF8 to stop drawing circles LDA K \ If the radius in K <= 160, loop back to HFL2 to draw CMP #160 \ another one BCC HFL2 .HF8 RTS \ Return from the subroutine }
Name: STARS2 [View individually] Type: Subroutine Category: Stardust Summary: Process the stardust for the left or right view
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. 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 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 \ Set S = P but with the sign from RAT2, so we now have EOR RAT2 \ the distance delta_x with the correct sign in (S R): STA S \ \ (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 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 STA SXL,Y \ Store the low byte X in x_lo \ So (XX+1 x_lo) now contains result 5 above: \ \ x = x - alpha * x * y 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 JSR PIX1 \ Call PIX1 to 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 \ \ PIX1 also draws a particle at (X1, Y1) with distance \ ZZ, which will remove the old stardust particle, as we \ set X1, Y1 and ZZ to the original values for this \ particle during the calculations above \ 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 \ If |x_hi| >= 116 then jump to KILL2 to recycle this CMP #116 \ particle, as it's gone off the side of the screen, BCS KILL2 \ and re-join at STC2 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| >= 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 re-join 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 #110 \ Make sure A is at least 110 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: SNE [View individually] Type: Variable Category: Maths (Geometry) Summary: Sine/cosine table
To calculate the following: sin(theta) * 256 where theta is in radians, look up the value in: SNE + (theta * 10) To calculate the following: cos(theta) * 256 where theta is in radians, look up the value in: SNE + ((theta * 10) + 16) mod 32 Theta must be between 0 and 3.1 radians, so theta * 10 is between 0 and 31.
.SNE { FOR I%, 0, 31 N = ABS(SIN((I% / 64) * 2 * PI)) IF N >= 1 EQUB &FF ELSE EQUB INT(256 * N + 0.5) ENDIF NEXT }
Name: MU5 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Set K(3 2 1 0) = (A A A A) and clear the C flGag
In practice this is only called via a BEQ following an AND instruction, in which case A = 0, so this routine effectively does this: K(3 2 1 0) = 0
.MU5 { STA K \ Set K(3 2 1 0) to (A A A A) STA K+1 STA K+2 STA K+3 CLC \ Clear the C flag RTS \ Return from the subroutine }
Name: MULT3 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate K(3 2 1 0) = (A P+1 P) * Q
Calculate the following multiplication between a signed 24-bit number and a signed 8-bit number, returning the result as a signed 32-bit number: K(3 2 1 0) = (A P+1 P) * Q The algorithm is the same shift-and-add algorithm as in routine MULT1, but extended to cope with more bits. Returns: C flag The C flag is cleared
.MULT3 { STA R \ Store the high byte of (A P+1 P) in R AND #%01111111 \ Set K+2 to |A|, the high byte of K(2 1 0) STA K+2 LDA Q \ Set A to bits 0-6 of Q, so A = |Q| AND #%01111111 BEQ MU5 \ If |Q| = 0, jump to MU5 to set K(3 2 1 0) to 0, \ returning from the subroutine with a tail call SEC \ Set T = |Q| - 1 SBC #1 STA T \ We now use the same shift-and-add algorithm as MULT1 \ to calculate the following: \ \ K(2 1 0) = K(2 1 0) * |Q| \ \ so we start with the first shift right, in which we \ take (K+2 P+1 P) and shift it right, storing the \ result in K(2 1 0), ready for the multiplication loop \ (so the multiplication loop actually calculates \ (|A| P+1 P) * |Q|, as the following sets K(2 1 0) to \ (|A| P+1 P) shifted right) LDA P+1 \ Set A = P+1 LSR K+2 \ Shift the high byte in K+2 to the right ROR A \ Shift the middle byte in A to the right and store in STA K+1 \ K+1 (so K+1 contains P+1 shifted right) LDA P \ Shift the middle byte in P to the right and store in ROR A \ K, so K(2 1 0) now contains (|A| P+1 P) shifted right STA K \ We now use the same shift-and-add algorithm as MULT1 \ to calculate the following: \ \ K(2 1 0) = K(2 1 0) * |Q| LDA #0 \ Set A = 0 so we can start building the answer in A LDX #24 \ Set up a counter in X to count the 24 bits in K(2 1 0) .MUL2 BCC P%+4 \ If C (i.e. the next bit from K) is set, do the ADC T \ addition for this bit of K: \ \ A = A + T + C \ = A + |Q| - 1 + 1 \ = A + |Q| ROR A \ Shift A right by one place to catch the next digit ROR K+2 \ next digit of our result in the left end of K(2 1 0), ROR K+1 \ while also shifting K(2 1 0) right to fetch the next ROR K \ bit for the calculation into the C flag \ \ On the last iteration of this loop, the bit falling \ off the end of K will be bit 0 of the original A, as \ we did one shift before the loop and we are doing 24 \ iterations. We set A to 0 before looping, so this \ means the loop exits with the C flag clear DEX \ Decrement the loop counter BNE MUL2 \ Loop back for the next bit until K(2 1 0) has been \ rotated all the way \ The result (|A| P+1 P) * |Q| is now in (A K+2 K+1 K), \ but it is positive and doesn't have the correct sign \ of the final result yet STA T \ Save the high byte of the result into T LDA R \ Fetch the sign byte from the original (A P+1 P) \ argument that we stored in R EOR Q \ EOR with Q so the sign bit is the same as that of \ (A P+1 P) * Q AND #%10000000 \ Extract the sign bit ORA T \ Apply this to the high byte of the result in T, so \ that A now has the correct sign for the result, and \ (A K+2 K+1 K) therefore contains the correctly signed \ result STA K+3 \ Store A in K+3, so K(3 2 1 0) now contains the result RTS \ Return from the subroutine }
Name: MLS2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (S R) = XX(1 0) and (A P) = A * ALP1
Calculate the following: (S R) = XX(1 0) (A P) = A * ALP1 where ALP1 is the magnitude of the current roll angle alpha, in the range 0-31.
.MLS2 { LDX XX \ Set (S R) = XX(1 0), starting with the low bytes STX R LDX XX+1 \ And then doing the high bytes STX S \ Fall through into MLS1 to calculate (A P) = A * ALP1 }
Name: MLS1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = ALP1 * A
Calculate the following: (A P) = ALP1 * A where ALP1 is the magnitude of the current roll angle alpha, in the range 0-31. This routine uses an unrolled version of MU11. MU11 calculates P * X, so we use the same algorithm but with P set to ALP1 and X set to A. The unrolled version here can skip the bit tests for bits 5-7 of P as we know P < 32, so only 5 shifts with bit tests are needed (for bits 0-4), while the other 3 shifts can be done without a test (for bits 5-7). Other entry points: MULTS-2 Calculate (A P) = X * A
.MLS1 { LDX ALP1 \ Set P to the roll angle alpha magnitude in ALP1 STX P \ (0-31), so now we calculate P * A .^MULTS TAX \ Set X = A, so now we can calculate P * X instead of \ P * A to get our result, and we can use the algorithm \ from MU11 to do that, just unrolled (as MU11 returns \ P * X) AND #%10000000 \ Set T to the sign bit of A STA T TXA \ Set A = |A| AND #127 BEQ MU6 \ If A = 0, jump to MU6 to set P(1 0) = 0 and return \ from the subroutine using a tail call TAX \ Set T1 = X - 1 DEX \ STX T1 \ We subtract 1 as carry will be set when we want to do \ an addition in the loop below LDA #0 \ Set A = 0 so we can start building the answer in A LSR P \ Set P = P >> 1 \ and carry = bit 0 of P \ We are now going to work our way through the bits of \ P, and do a shift-add for any bits that are set, \ keeping the running total in A, but instead of using a \ loop like MU11, we just unroll it, starting with bit 0 BCC P%+4 \ If C (i.e. the next bit from P) is set, do the ADC T1 \ addition for this bit of P: \ \ A = A + T1 + C \ = A + X - 1 + 1 \ = A + X ROR A \ Shift A right to catch the next digit of our result, \ which the next ROR sticks into the left end of P while \ also extracting the next bit of P ROR P \ Add the overspill from shifting A to the right onto \ the start of P, and shift P right to fetch the next \ bit for the calculation into the C flag BCC P%+4 \ Repeat the shift-and-add loop for bit 1 ADC T1 ROR A ROR P BCC P%+4 \ Repeat the shift-and-add loop for bit 2 ADC T1 ROR A ROR P BCC P%+4 \ Repeat the shift-and-add loop for bit 3 ADC T1 ROR A ROR P BCC P%+4 \ Repeat the shift-and-add loop for bit 4 ADC T1 ROR A ROR P LSR A \ Just do the "shift" part for bit 5 ROR P LSR A \ Just do the "shift" part for bit 6 ROR P LSR A \ Just do the "shift" part for bit 7 ROR P ORA T \ Give A the sign bit of the original argument A that \ we put into T above RTS \ Return from the subroutine }
Name: SQUA [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Clear bit 7 of A and calculate (A P) = A * A
Do the following multiplication of unsigned 8-bit numbers, after first clearing bit 7 of A: (A P) = A * A
.SQUA { AND #%01111111 \ Clear bit 7 of A and fall through into SQUA2 to set \ (A P) = A * A }
Name: SQUA2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = A * A
Do the following multiplication of unsigned 8-bit numbers: (A P) = A * A
.SQUA2 { STA P \ Copy A into P and X TAX BNE MU11 \ If X = 0 fall through into MU1 to return a 0, \ otherwise jump to MU11 to return P * X }
Name: MU1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Copy X into P and A, and clear the C flag
Used to return a 0 result quickly from MULTU below.
.MU1 { CLC \ Clear the C flag STX P \ Copy X into P and A TXA RTS \ Return from the subroutine }
Name: MLU1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate Y1 = y_hi and (A P) = |y_hi| * Q for Y-th stardust
Do the following assignment, and multiply the Y-th stardust particle's y-coordinate with an unsigned number Q: Y1 = y_hi (A P) = |y_hi| * Q
.MLU1 { LDA SY,Y \ Set Y1 the Y-th byte of SY STA Y1 \ Fall through into MLU2 to calculate: \ \ (A P) = |A| * Q }
Name: MLU2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = |A| * Q
Do the following multiplication of a sign-magnitude 8-bit number P with an unsigned number Q: (A P) = |A| * Q
.MLU2 { AND #%01111111 \ Clear the sign bit in P, so P = |A| STA P \ Fall through into MULTU to calculate: \ \ (A P) = P * Q \ = |A| * Q }
Name: MULTU [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = P * Q
Do the following multiplication of unsigned 8-bit numbers: (A P) = P * Q
.MULTU { LDX Q \ Set X = Q BEQ MU1 \ If X = Q = 0, jump to MU1 to copy X into P and A, \ clear the C flag and return from the subroutine using \ a tail call \ Otherwise fall through into MU11 to set (A P) = P * X }
Name: MU11 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = P * X
Do the following multiplication of two unsigned 8-bit numbers: (A P) = P * X This uses the same shift-and-add approach as MULT1, but it's simpler as we are dealing with unsigned numbers in P and X. See the deep dive on "Shift-and-add multiplication" for a discussion of how this algorithm works.
.MU11 { DEX \ Set T = X - 1 STX T \ \ We subtract 1 as carry will be set when we want to do \ an addition in the loop below LDA #0 \ Set A = 0 so we can start building the answer in A LDX #8 \ Set up a counter in X to count the 8 bits in P LSR P \ Set P = P >> 1 \ and carry = bit 0 of P \ We are now going to work our way through the bits of \ P, and do a shift-add for any bits that are set, \ keeping the running total in A. We just did the first \ shift right, so we now need to do the first add and \ loop through the other bits in P .MUL6 BCC P%+4 \ If C (i.e. the next bit from P) is set, do the ADC T \ addition for this bit of P: \ \ A = A + T + C \ = A + X - 1 + 1 \ = A + X ROR A \ Shift A right to catch the next digit of our result, \ which the next ROR sticks into the left end of P while \ also extracting the next bit of P ROR P \ Add the overspill from shifting A to the right onto \ the start of P, and shift P right to fetch the next \ bit for the calculation into the C flag DEX \ Decrement the loop counter BNE MUL6 \ Loop back for the next bit until P has been rotated \ all the way RTS \ Return from the subroutine }
Name: MU6 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Set P(1 0) = (A A)
In practice this is only called via a BEQ following an AND instruction, in which case A = 0, so this routine effectively does this: P(1 0) = 0
.MU6 { STA P+1 \ Set P(1 0) = (A A) STA P RTS \ Return from the subroutine }
Name: FMLTU2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate A = K * sin(A)
Calculate the following: A = K * sin(A) Because this routine uses the sine lookup table SNE, we can also call this routine to calculate cosine multiplication. To calculate the following: A = K * cos(B) call this routine with B + 16 in the accumulator, as sin(B + 16) = cos(B).
.FMLTU2 { AND #%00011111 \ Restrict A to bits 0-5 (so it's in the range 0-31) TAX \ Set Q = sin(A) * 256 LDA SNE,X STA Q LDA K \ Set A to the radius in K \ Fall through into FMLTU to do the following: \ \ (A ?) = A * Q \ = K * sin(A) * 256 \ which is equivalent to: \ \ A = K * sin(A) }
Name: FMLTU [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate A = A * Q / 256
Do the following multiplication of two unsigned 8-bit numbers, returning only the high byte of the result: (A ?) = A * Q or, to put it another way: A = A * Q / 256
.FMLTU { EOR #%11111111 \ Flip the bits in A, set the C flag and rotate right, SEC \ so the C flag now contains bit 0 of A inverted, and P ROR A \ contains A inverted and shifted right by one, with bit STA P \ 7 set to a 1. We can now use P as our source of bits \ to shift right, just as in MU11, just with the logic \ reversed LDA #0 \ Set A = 0 so we can start building the answer in A .MUL3 BCS MU7 \ If C (i.e. the next bit from P) is set, do not do the \ addition for this bit of P, and instead skip to MU7 \ to just do the shifts ADC Q \ Do the addition for this bit of P: \ \ A = A + Q + C \ = A + Q ROR A \ Shift A right to catch the next digit of our result. \ If we were interested in the low byte of the result we \ would want to save the bit that falls off the end, but \ we aren't, so we can ignore it LSR P \ Shift P right to fetch the next bit for the \ calculation into the C flag BNE MUL3 \ Loop back to MUL3 if P still contains some set bits \ (so we loop through the bits of P until we get to the \ 1 we inserted before the loop, and then we stop) RTS \ Return from the subroutine .MU7 LSR A \ Shift A right to catch the next digit of our result, \ pushing a 0 into bit 7 as we aren't adding anything \ here (we can't use a ROR here as the C flag is set, so \ a ROR would push a 1 into bit 7) LSR P \ Fetch the next bit from P into the C flag BNE MUL3 \ Loop back to MUL3 if P still contains some set bits \ (so we loop through the bits of P until we get to the \ 1 we inserted before the loop, and then we stop) RTS \ Return from the subroutine }
Name: Unused duplicate of MULTU [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Unused duplicate of the MULTU routine
This is a duplicate of the MULTU routine, but with no entry label, so it can't be called by name. It is unused, and could have been culled to save a few bytes (24 to be precise), but it's still here. In the disc version it has the label MULTU6, but here in the cassette version it's unnamed, unloved and unvisited, through no fault of its own.
{ LDX Q BEQ MU1 DEX STX T LDA #0 LDX #8 LSR P .MUL6 BCC P%+4 ADC T ROR A ROR P DEX BNE MUL6 RTS }
Name: MLTU2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P+1 P) = (A ~P) * Q
Do the following multiplication of an unsigned 16-bit number and an unsigned 8-bit number: (A P+1 P) = (A ~P) * Q where ~P means P EOR %11111111 (i.e. P with all its bits flipped). In other words, if you wanted to calculate &1234 * &56, you would: * Set A to &12 * Set P to &34 EOR %11111111 = &CB * Set Q to &56 before calling MLTU2. This routine is like a mash-up of MU11 and FMLTU. It uses part of FMLTU's inverted argument trick to work out whether or not to do an addition, and like MU11 it sets up a counter in X to extract bits from (P+1 P). But this time we extract 16 bits from (P+1 P), so the result is a 24-bit number. The core of the algorithm is still the shift-and-add approach explained in MULT1, just with more bits. Returns: Q Q is preserved Other entry points: MLTU2-2 Set Q to X, so this calculates (A P+1 P) = (A ~P) * X
{ STX Q \ Store X in Q .^MLTU2 EOR #%11111111 \ Flip the bits in A and rotate right, storing the LSR A \ result in P+1, so we now calculate (P+1 P) * Q STA P+1 LDA #0 \ Set A = 0 so we can start building the answer in A LDX #16 \ Set up a counter in X to count the 16 bits in (P+1 P) ROR P \ Set P = P >> 1 with bit 7 = bit 0 of A \ and carry = bit 0 of P .MUL7 BCS MU21 \ If C (i.e. the next bit from P) is set, do not do the \ addition for this bit of P, and instead skip to MU21 \ to just do the shifts ADC Q \ Do the addition for this bit of P: \ \ A = A + Q + C \ = A + Q ROR A \ Rotate (A P+1 P) to the right, so we capture the next ROR P+1 \ digit of the result in P+1, and extract the next digit ROR P \ of (P+1 P) in the C flag DEX \ Decrement the loop counter BNE MUL7 \ Loop back for the next bit until P has been rotated \ all the way RTS \ Return from the subroutine .MU21 LSR A \ Shift (A P+1 P) to the right, so we capture the next ROR P+1 \ digit of the result in P+1, and extract the next digit ROR P \ of (P+1 P) in the C flag DEX \ Decrement the loop counter BNE MUL7 \ Loop back for the next bit until P has been rotated \ all the way RTS \ Return from the subroutine }
Name: MUT3 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Unused routine that does the same as MUT2
This routine is never actually called, but it is identical to MUT2, as the extra instructions have no effect.
.MUT3 { LDX ALP1 \ Set P = ALP1, though this gets overwritten by the STX P \ following, so this has no effect \ Fall through into MUT2 to do the following: \ \ (S R) = XX(1 0) \ (A P) = Q * A }
Name: MUT2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (S R) = XX(1 0) and (A P) = Q * A
Do the following assignment, and multiplication of two signed 8-bit numbers: (S R) = XX(1 0) (A P) = Q * A
.MUT2 { LDX XX+1 \ Set S = XX+1 STX S \ Fall through into MUT1 to do the following: \ \ R = XX \ (A P) = Q * A }
Name: MUT1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate R = XX and (A P) = Q * A
Do the following assignment, and multiplication of two signed 8-bit numbers: R = XX (A P) = Q * A
.MUT1 { LDX XX \ Set R = XX STX R \ Fall through into MULT1 to do the following: \ \ (A P) = Q * A }
Name: MULT1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A P) = Q * A
Do the following multiplication of two 8-bit sign-magnitude numbers: (A P) = Q * A
.MULT1 { TAX \ Store A in X AND #%01111111 \ Set P = |A| >> 1 LSR A \ and carry = bit 0 of A STA P TXA \ Restore argument A EOR Q \ Set bit 7 of A and T if Q and A have different signs, AND #%10000000 \ clear bit 7 if they have the same signs, 0 all other STA T \ bits, i.e. T contains the sign bit of Q * A LDA Q \ Set A = |Q| AND #%01111111 BEQ mu10 \ If |Q| = 0 jump to mu10 (with A set to 0) TAX \ Set T1 = |Q| - 1 DEX \ STX T1 \ We subtract 1 as carry will be set when we want to do \ an addition in the loop below \ We are now going to work our way through the bits of \ P, and do a shift-add for any bits that are set, \ keeping the running total in A. We already set up \ the first shift at the start of this routine, as \ P = |A| >> 1 and C = bit 0 of A, so we now need to set \ up a loop to sift through the other 7 bits in P LDA #0 \ Set A = 0 so we can start building the answer in A LDX #7 \ Set up a counter in X to count the 7 bits remaining \ in P .MUL4 BCC P%+4 \ If C (i.e. the next bit from P) is set, do the ADC T1 \ addition for this bit of P: \ \ A = A + T1 + C \ = A + |Q| - 1 + 1 \ = A + |Q| ROR A \ As mentioned above, this ROR shifts A right and \ catches bit 0 in C - giving another digit for our \ result - and the next ROR sticks that bit into the \ left end of P while also extracting the next bit of P \ for the next addition ROR P \ Add the overspill from shifting A to the right onto \ the start of P, and shift P right to fetch the next \ bit for the calculation DEX \ Decrement the loop counter BNE MUL4 \ Loop back for the next bit until P has been rotated \ all the way LSR A \ Rotate (A P) once more to get the final result, as ROR P \ we only pushed 7 bits through the above process ORA T \ Set the sign bit of the result that we stored in T RTS \ Return from the subroutine .mu10 STA P \ If we get here, the result is 0 and A = 0, so set \ P = 0 so (A P) = 0 RTS \ Return from the subroutine }
Name: MULT12 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (S R) = Q * A
Calculate: (S R) = Q * A
.MULT12 { JSR MULT1 \ Set (A P) = Q * A STA S \ Set (S R) = (A P) LDA P STA R RTS \ Return from the subroutine }
Name: TAS3 [View individually] Type: Subroutine Category: Maths (Geometry) Summary: Calculate the dot product of XX15 and an orientation vector
Calculate the dot product of the vector in XX15 and one of the orientation vectors, as determined by the value of Y. If vect is the orientation vector, we calculate this: (A X) = vect . XX15 = vect_x * XX15 + vect_y * XX15+1 + vect_z * XX15+2 Arguments: Y The orientation vector: * If Y = 10, calculate nosev . XX15 * If Y = 16, calculate roofv . XX15 * If Y = 22, calculate sidev . XX15 Returns: (A X) The result of the dot product
.TAS3 { LDX INWK,Y \ Set Q = the Y-th byte of INWK, i.e. vect_x STX Q LDA XX15 \ Set A = XX15 JSR MULT12 \ Set (S R) = Q * A = LDX INWK+2,Y \ Set Q = the Y+2-th byte of INWK, i.e. vect_y STX Q LDA XX15+1 \ Set A = XX15+1 JSR MAD \ Set (A X) = Q * A + (S R) \ = vect_y * XX15+1 + vect_x * XX15 STA S \ Set (S R) = (A X) STX R LDX INWK+4,Y \ Set Q = the Y+2-th byte of INWK, i.e. vect_z STX Q LDA XX15+2 \ Set A = XX15+2 \ Fall through into MAD to set: \ \ (A X) = Q * A + (S R) \ = vect_z * XX15+2 + vect_y * XX15+1 + \ vect_x * XX15 }
Name: MAD [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A X) = Q * A + (S R)
Calculate (A X) = Q * A + (S R)
.MAD { JSR MULT1 \ Call MULT1 to set (A P) = Q * A \ Fall through into ADD to do: \ \ (A X) = (A P) + (S R) \ = Q * A + (S R) }
Name: ADD [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A X) = (A P) + (S R)
Add two signed 16-bit numbers together, making sure the result has the correct sign. Specifically: (A X) = (A P) + (S R)
.ADD { STA T1 \ Store argument A in T1 AND #%10000000 \ Extract the sign (bit 7) of A and store it in T STA T EOR S \ EOR bit 7 of A with S. If they have different bit 7s BMI MU8 \ (i.e. they have different signs) then bit 7 in the \ EOR result will be 1, which means the EOR result is \ negative. So the AND, EOR and BMI together mean "jump \ to MU8 if A and S have different signs" \ If we reach here, then A and S have the same sign, so \ we can add them and set the sign to get the result LDA R \ Add the least significant bytes together into X, so CLC \ ADC P \ X = P + R TAX LDA S \ Add the most significant bytes together into A. We ADC T1 \ stored the original argument A in T1 earlier, so we \ can do this with: \ \ A = A + S + carry \ = T1 + S + carry ORA T \ If argument A was negative (and therefore S was also \ negative) then make sure result A is negative by \ OR-ing the result with the sign bit from argument A \ (which we stored in T) RTS \ Return from the subroutine .MU8 \ If we reach here, then A and S have different signs, \ so we can subtract their absolute values and set the \ sign to get the result LDA S \ Clear the sign (bit 7) in S and store the result in AND #%01111111 \ U, so U now contains |S| STA U LDA P \ Subtract the least significant bytes into X, so SEC \ X = P - R SBC R TAX LDA T1 \ Restore the A of the argument (A P) from T1 and AND #%01111111 \ clear the sign (bit 7), so A now contains |A| SBC U \ Set A = |A| - |S| \ At this point we have |A P| - |S R| in (A X), so we \ need to check whether the subtraction above was the \ the right way round (i.e. that we subtracted the \ smaller absolute value from the larger absolute \ value) BCS MU9 \ If |A| >= |S|, our subtraction was the right way \ round, so jump to MU9 to set the sign \ If we get here, then |A| < |S|, so our subtraction \ above was the wrong way round (we actually subtracted \ the larger absolute value from the smaller absolute \ value. So let's subtract the result we have in (A X) \ from zero, so that the subtraction is the right way \ round STA U \ Store A in U TXA \ Set X = 0 - X using two's complement (to negate a EOR #&FF \ number in two's complement, you can invert the bits ADC #1 \ and add one - and we know carry is clear as we didn't TAX \ take the BCS branch above, so ADC will do the job) LDA #0 \ Set A = 0 - A, which we can do this time using a SBC U \ a subtraction with carry clear ORA #%10000000 \ We now set the sign bit of A, so that the EOR on the \ next line will give the result the opposite sign to \ argument A (as T contains the sign bit of argument \ A). This is the same as giving the result the same \ sign as argument S (as A and S have different signs), \ which is what we want, as S has the larger absolute \ value .MU9 EOR T \ If we get here from the BCS above, then |A| >= |S|, \ so we want to give the result the same sign as \ argument A, so if argument A was negative, we flip \ the sign of the result with an EOR (to make it \ negative) RTS \ Return from the subroutine }
Name: TIS1 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (A ?) = (-X * A + (S R)) / 96
Calculate the following expression between sign-magnitude numbers, ignoring the low byte of the result: (A ?) = (-X * A + (S R)) / 96 This uses the same shift-and-subtract algorithm as TIS2, just with the quotient A hard-coded to 96. Returns: Q Gets set to the value of argument X
.TIS1 { STX Q \ Set Q = X EOR #%10000000 \ Flip the sign bit in A JSR MAD \ Set (A X) = Q * A + (S R) \ = X * -A + (S R) .DVID96 TAX \ Set T to the sign bit of the result AND #%10000000 STA T TXA \ Set A to the high byte of the result with the sign bit AND #%01111111 \ cleared, so (A ?) = |X * A + (S R)| \ The following is identical to TIS2, except Q is \ hard-coded to 96, so this does A = A / 96 LDX #254 \ Set T1 to have bits 1-7 set, so we can rotate through STX T1 \ 7 loop iterations, getting a 1 each time, and then \ getting a 0 on the 8th iteration... and we can also \ use T1 to catch our result bits into bit 0 each time .DVL3 ASL A \ Shift A to the left CMP #96 \ If A < 96 skip the following subtraction BCC DV4 SBC #96 \ Set A = A - 96 \ \ Going into this subtraction we know the C flag is \ set as we passed through the BCC above, and we also \ know that A >= 96, so the C flag will still be set \ once we are done .DV4 ROL T1 \ Rotate the counter in T1 to the left, and catch the \ result bit into bit 0 (which will be a 0 if we didn't \ do the subtraction, or 1 if we did) BCS DVL3 \ If we still have set bits in T1, loop back to DVL3 to \ do the next iteration of 7 LDA T1 \ Fetch the result from T1 into A ORA T \ Give A the sign of the result that we stored above RTS \ Return from the subroutine }
Name: DV42 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (P R) = 256 * DELTA / z_hi
Calculate the following division and remainder: P = DELTA / (the Y-th stardust particle's z_hi coordinate) R = remainder as a fraction of A, where 1.0 = 255 Another way of saying the above is this: (P R) = 256 * DELTA / z_hi DELTA is a value between 1 and 40, and the minimum z_hi is 16 (dust particles are removed at lower values than this), so this means P is between 0 and 2 (as 40 / 16 = 2.5, so the maximum result is P = 2 and R = 128. This uses the same shift-and-subtract algorithm as TIS2, but this time we keep the remainder. Arguments: Y The number of the stardust particle to process Returns: C flag The C flag is cleared
.DV42 { LDA SZ,Y \ Fetch the Y-th dust particle's z_hi coordinate into A }
Name: DV41 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (P R) = 256 * DELTA / A
Calculate the following division and remainder: P = DELTA / A R = remainder as a fraction of A, where 1.0 = 255 Another way of saying the above is this: (P R) = 256 * DELTA / A This uses the same shift-and-subtract algorithm as TIS2, but this time we keep the remainder. Returns: C flag The C flag is cleared
.DV41 { STA Q \ Store A in Q LDA DELTA \ Fetch the speed from DELTA into A }
Name: DVID4 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate (P R) = 256 * A / Q
Calculate the following division and remainder: P = A / Q R = remainder as a fraction of Q, where 1.0 = 255 Another way of saying the above is this: (P R) = 256 * A / Q This uses the same shift-and-subtract algorithm as TIS2, but this time we keep the remainder. Returns: C flag The C flag is cleared
.DVID4 { LDX #8 \ Set a counter in X to count the 8 bits in A ASL A \ Shift A left and store in P (we will build the result STA P \ in P) LDA #0 \ Set A = 0 for us to build a remainder .DVL4 ROL A \ Shift A to the left BCS DV8 \ If the C flag is set (i.e. bit 7 of A was set) then \ skip straight to the subtraction CMP Q \ If A < Q skip the following subtraction BCC DV5 .DV8 SBC Q \ A >= Q, so set A = A - Q SEC \ Set the C flag, so that P gets a 1 shifted into bit 0 .DV5 ROL P \ Shift P to the left, pulling the C flag into bit 0 DEX \ Decrement the loop counter BNE DVL4 \ Loop back for the next bit until we have done all 8 \ bits of P JMP LL28+4 \ Jump to LL28+4 to convert the remainder in A into an \ integer representation of the fractional value A / Q, \ in R, where 1.0 = 255. LL28+4 always returns with the \ C flag cleared, and we return from the subroutine \ using a tail call }
Name: DVID3B2 [View individually] Type: Subroutine Category: Maths (Arithmetic) Summary: Calculate K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_lo)
Calculate the following: K(3 2 1 0) = (A P+1 P) / (z_sign z_hi z_lo) The actual division here is done as an 8-bit calculation using LL31, but this routine shifts both the numerator (the top part of the division) and the denominator (the bottom part of the division) around to get the multi-byte result we want. Specifically, it shifts both of them to the left as far as possible, keeping a tally of how many shifts get done in each one - and specifically, the difference in the number of shifts between the top and bottom (as shifting both of them once in the same direction won't change the result). It then divides the two highest bytes with the simple 8-bit routine in LL31, and shifts the result by the difference in the number of shifts, which acts as a scale factor to get the correct result. Returns: K(3 2 1 0) The result of the division X X is preserved
.DVID3B2 { STA P+2 \ Set P+2 = A LDA INWK+6 \ Set Q = z_lo STA Q LDA INWK+7 \ Set R = z_hi STA R LDA INWK+8 \ Set S = z_sign STA S .DVID3B \ Given the above assignments, we now want to calculate \ the following to get the result we want: \ \ K(3 2 1 0) = P(2 1 0) / (S R Q) LDA P \ Make sure P(2 1 0) is at least 1 ORA #1 STA P LDA P+2 \ Set T to the sign of P+2 * S (i.e. the sign of the EOR S \ result) and store it in T AND #%10000000 STA T LDY #0 \ Set Y = 0 to store the scale factor LDA P+2 \ Clear the sign bit of P+2, so the division can be done AND #%01111111 \ with positive numbers and we'll set the correct sign \ below, once all the maths is done \ \ This also leaves A = P+2, which we use below .DVL9 \ We now shift (A P+1 P) left until A >= 64, counting \ the number of shifts in Y. This makes the top part of \ the division as large as possible, thus retaining as \ much accuracy as we can. When we come to return the \ final result, we shift the result by the number of \ places in Y, and in the correct direction CMP #64 \ If A >= 64, jump down to DV14 BCS DV14 ASL P \ Shift (A P+1 P) to the left ROL P+1 ROL A INY \ Increment the scale factor in Y BNE DVL9 \ Loop up to DVL9 (this BNE is effectively a JMP, as Y \ will never be zero) .DV14 \ If we get here, A >= 64 and contains the highest byte \ of the numerator, scaled up by the number of left \ shifts in Y STA P+2 \ Store A in P+2, so we now have the scaled value of \ the numerator in P(2 1 0) LDA S \ Set A = |S| AND #%01111111 BMI DV9 \ If bit 7 of A is set, jump down to DV9 to skip the \ left-shifting of the denominator (though this branch \ instruction has no effect as bit 7 of the above AND \ can never be set) .DVL6 \ We now shift (S R Q) left until bit 7 of S is set, \ reducing Y by the number of shifts. This makes the \ bottom part of the division as large as possible, thus \ retaining as much accuracy as we can. When we come to \ return the final result, we shift the result by the \ total number of places in Y, and in the correct \ direction, to give us the correct result \ \ We set A to |S| above, so the following actually \ shifts (A R Q) DEY \ Decrement the scale factor in Y ASL Q \ Shift (A R Q) to the left ROL R ROL A BPL DVL6 \ Loop up to DVL6 to do another shift, until bit 7 of A \ is set and we can't shift left any further .DV9 \ We have now shifted both the numerator and denominator \ left as far as they will go, keeping a tally of the \ overall scale factor of the various shifts in Y. We \ can now divide just the two highest bytes to get our \ result STA Q \ Set Q = A, the highest byte of the denominator LDA #254 \ Set R to have bits 1-7 set, so we can pass this to STA R \ LL31 to act as the bit counter in the division LDA P+2 \ Set A to the highest byte of the numerator JSR LL31 \ Call LL31 to calculate: \ \ R = 256 * A / Q \ = 256 * numerator / denominator \ The result of our division is now in R, so we just \ need to shift it back by the scale factor in Y LDA #0 \ Set K(3 2 1) = 0 to hold the result (we populate K STA K+1 \ next) STA K+2 STA K+3 TYA \ If Y is positive, jump to DV12 BPL DV12 \ If we get here then Y is negative, so we need to shift \ the result R to the left by Y places, and then set the \ correct sign for the result LDA R \ Set A = R .DVL8 ASL A \ Shift (K+3 K+2 K+1 A) left ROL K+1 ROL K+2 ROL K+3 INY \ Increment the scale factor in Y BNE DVL8 \ Loop back to DVL8 until we have shifted left by Y \ places STA K \ Store A in K so the result is now in K(3 2 1 0) LDA K+3 \ Set K+3 to the sign in T, which we set above to the ORA T \ correct sign for the result STA K+3 RTS \ Return from the subroutine .DV13 \ If we get here then Y is zero, so we don't need to \ shift the result R, we just need to set the correct \ sign for the result LDA R \ Store R in K so the result is now in K(3 2 1 0) STA K LDA T \ Set K+3 to the sign in T, which we set above to the STA K+3 \ correct sign for the result RTS \ Return from the subroutine .DV12 BEQ DV13 \ We jumped here having set A to the scale factor in Y, \ so this jumps up to DV13 if Y = 0 \ If we get here then Y is positive and non-zero, so we \ need to shift the result R to the right by Y places \ and then set the correct sign for the result. We also \ know that K(3 2 1) will stay 0, as we are shifting the \ lowest byte to the right, so no set bits will make \ their way into the top three bytes LDA R \ Set A = R .DVL10 LSR A \ Shift A right DEY \ Decrement the scale factor in Y BNE DVL10 \ Loop back to DVL10 until we have shifted right by Y \ places STA K \ Store the shifted A in K so the result is now in \ K(3 2 1 0) LDA T \ Set K+3 to the sign in T, which we set above to the STA K+3 \ correct sign for the result RTS \ Return from the subroutine }
Name: cntr [View individually] Type: Subroutine Category: Dashboard Summary: Apply damping to the pitch or roll dashboard indicator
Apply damping to the value in X, where X ranges from 1 to 255 with 128 as the centre point (so X represents a position on a centre-based dashboard slider, such as pitch or roll). If the value is in the left-hand side of the slider (1-127) then it bumps the value up by 1 so it moves towards the centre, and if it's in the right-hand side, it reduces it by 1, also moving it towards the centre.
.cntr { LDA DAMP \ If DAMP is non-zero, then keyboard damping is not BNE RE1 \ enabled, so jump to RE1 to return from the subroutine TXA \ If X < 128, then it's in the left-hand side of the BPL BUMP \ dashboard slider, so jump to BUMP to bump it up by 1, \ to move it closer to the centre DEX \ Otherwise X >= 128, so it's in the right-hand side BMI RE1 \ of the dashboard slider, so decrement X by 1, and if \ it's still >= 128, jump to RE1 to return from the \ subroutine, otherwise fall through to BUMP to undo \ the bump and then return .BUMP INX \ Bump X up by 1, and if it hasn't overshot the end of BNE RE1 \ the dashboard slider, jump to RE1 to return from the \ subroutine, otherwise fall through to REDU to drop \ it down by 1 again .REDU DEX \ Reduce X by 1, and if we have reached 0 jump up to BEQ BUMP \ BUMP to add 1, because we need the value to be in the \ range 1 to 255 .RE1 RTS \ Return from the subroutine }
Name: BUMP2 [View individually] Type: Subroutine Category: Dashboard Summary: Bump up the value of the pitch or roll dashboard indicator
Increase ("bump up") X by A, where X is either the current rate of pitch or the current rate of roll. The rate of pitch or roll ranges from 1 to 255 with 128 as the centre point. This is the amount by which the pitch or roll is currently changing, so 1 means it is decreasing at the maximum rate, 128 means it is not changing, and 255 means it is increasing at the maximum rate. These values correspond to the line on the DC or RL indicators on the dashboard, with 1 meaning full left, 128 meaning the middle, and 255 meaning full right. If bumping up X would push it past 255, then X is set to 255. If keyboard auto-recentre is configured and the result is less than 128, we bump X up to the mid-point, 128. This is the equivalent of having a roll or pitch in the left half of the indicator, when increasing the roll or pitch should jump us straight to the mid-point. Other entry points: RE2+2 Restore A from T and return from the subroutine. Used by REDU2
.BUMP2 { STA T \ Store argument A in T so we can restore it later TXA \ Copy argument X into A CLC \ Clear the carry flag so we can do addition without \ the carry flag affecting the result ADC T \ Set X = A = argument X + argument A TAX BCC RE2 \ If carry is clear, then we didn't overflow, so jump \ to RE2 to auto-recentre and return the result LDX #255 \ We have an overflow, so set X to the maximum possible \ value, 255 .^RE2 BPL RE3+2 \ If X has bit 7 clear (i.e. the result < 128), then \ jump to RE3+2 below to do an auto-recentre, if \ configured, because the result is on the left side of \ the centre point of 128 \ Jumps to RE2+2 end up here LDA T \ Restore the original argument A into A RTS \ Return from the subroutine }
Name: REDU2 [View individually] Type: Subroutine Category: Dashboard Summary: Reduce the value of the pitch or roll dashboard indicator
Reduce X by A, where X is either the current rate of pitch or the current rate of roll. The rate of pitch or roll ranges from 1 to 255 with 128 as the centre point. This is the amount by which the pitch or roll is currently changing, so 1 means it is decreasing at the maximum rate, 128 means it is not changing, and 255 means it is increasing at the maximum rate. These values correspond to the line on the DC or RL indicators on the dashboard, with 1 meaning full left, 128 meaning the middle, and 255 meaning full right. If reducing X would bring it below 1, then X is set to 1. If keyboard auto-recentre is configured and the result is greater than 128, we reduce X down to the mid-point, 128. This is the equivalent of having a roll or pitch in the right half of the indicator, when decreasing the roll or pitch should jump us straight to the mid-point. Other entry points: RE3+2 Auto-recentre the value in X, if configured. Used by BUMP2
.REDU2 { STA T \ Store argument A in T so we can restore it later TXA \ Copy argument X into A SEC \ Set the carry flag so we can do subtraction without \ the carry flag affecting the result SBC T \ Set X = A = argument X - argument A TAX BCS RE3 \ If carry is set, then we didn't underflow, so jump \ to RE3 to auto-recentre and return the result LDX #1 \ We have an underflow, so set X to the minimum possible \ value, 1 .^RE3 BPL RE2+2 \ If X has bit 7 clear (i.e. the result < 128), then \ jump to RE2+2 above to return the result as is, \ because the result is on the left side of the centre \ point of 128, so we don't need to auto-centre \ Jumps to RE3+2 end up here \ If we get here, then we need to apply auto-recentre, \ if it is configured LDA DJD \ If keyboard auto-recentre is disabled, then BNE RE2+2 \ jump to RE2+2 to restore A and return LDX #128 \ If keyboard auto-recentre is enabled, set X to 128 BMI RE2+2 \ (the middle of our range) and jump to RE2+2 to \ restore A and return }
Name: ARCTAN [View individually] Type: Subroutine Category: Maths (Geometry) Summary: Calculate A = arctan(P / Q)
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
.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 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 .AR4 LDX T1 \ If T1 is negative, i.e. P and Q have different signs, BMI AR3 \ jump down to AR3 to change 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 ACT \ documentation below, 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 ACT documentation \ below, i.e. 128 - A STA T \ Set A = 128 - A LDA #128 \ \SEC \ The SEC instruction is commented out in the original SBC T \ source, and isn't required as we did a SEC before \ calling AR3 RTS \ Return from the subroutine .ARS1 \ This routine fetches arctan(A / Q) from the ACT table 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: ACT [View individually] Type: Variable Category: Maths (Geometry) Summary: Arctan table
To calculate the following: theta = arctan(t) where 0 <= t < 1, look up the value in: ACT + (t * 32) The result will be an integer representing the angle in radians, with 256 representing a full circle of 2 * PI radians. The table does not support values of t >= 1 or t < 0 directly, but we can use the following calculations instead: * For t > 1, arctan(t) = 64 - arctan(1 / t) * For t < 0, arctan(-t) = 128 - arctan(t) If t < -1, we can do the first one to get arctan(|t|), then the second to get arctan(-|t|).
.ACT { FOR I%, 0, 31 EQUB INT((128 / PI) * ATN(I% / 32) + 0.5) NEXT }
Name: WARP [View individually] Type: Subroutine Category: Flight Summary: Perform an in-system jump
This is called when we press "J" during flight. The following checks are performed: * Make sure we don't have any ships or space stations in the vicinity * Make sure we are not in witchspace * If we are facing the planet, make sure we aren't too close * If we are facing the sun, make sure we aren't too close If the above checks are passed, then we perform an in-system jump by moving the sun and planet in the opposite direction to travel, so we appear to jump in space. This means that any asteroids, cargo canisters or escape pods get dragged along for the ride.
.WARP { LDA MANY+AST \ Set X to the total number of asteroids, escape pods CLC \ and cargo canisters in the vicinity ADC MANY+ESC \ CLC \ The second CLC instruction appears in the BASIC ADC MANY+OIL \ source file (ELITEC), but not in the text source file TAX \ (ELITEC.TXT). The second CLC has no effect, as there \ is no way that adding the number of asteroids and the \ number escape pods will cause a carry, so presumably \ it got removed at some point LDA FRIN+2,X \ If the slot at FRIN+2+X is non-zero, then we have \ something else in the vicinity besides asteroids, \ escape pods and cargo canisters, so to check whether \ we can jump, we first grab the slot contents into A ORA SSPR \ If there is a space station nearby, then SSPR will \ be non-zero, so OR'ing with SSPR will produce a \ a non-zero result if either A or SSPR are non-zero ORA MJ \ If we are in witchspace, then MJ will be non-zero, so \ OR'ing with MJ will produce a non-zero result if \ either A or SSPR or MJ are non-zero BNE WA1 \ A is non-zero if we have either a ship or a space \ station in the vicinity, or we are in witchspace, in \ which case jump to WA1 to make a low beep to show that \ we can't do an in-system jump LDY K%+8 \ Otherwise we can do an in-system jump, so now we fetch \ the byte at K%+8, which contains the z_sign for the \ first ship slot, i.e. the distance of the planet BMI WA3 \ If the planet's z_sign is negative, then the planet \ is behind us, so jump to WA3 to skip the following TAY \ Set A = Y = 0 (as we didn't BNE above) so the call \ to MAS2 measures the distance to the planet JSR MAS2 \ Call MAS2 to set A to the largest distance to the \ planet in any of the three axes (we could also call \ routine m to do the same thing, as A = 0) \ The following two instructions appear in the BASIC \ source file (ELITEC), but in the text source file \ (ELITEC.TXT) they are replaced by: \ \ LSR A \ BEQ WA1 \ \ which does the same thing, but saves one byte of \ memory (as LSR A is a one-byte opcode, while CMP #2 \ takes up two bytes) CMP #2 \ If A < 2 then jump to WA1 to abort the in-system jump BCC WA1 \ with a low beep, as we are facing the planet and are \ too close to jump in that direction .WA3 LDY K%+NI%+8 \ Fetch the z_sign (byte #8) of the second ship in the \ ship data workspace at K%, which is reserved for the \ sun or the space station (in this case it's the \ former, as we already confirmed there isn't a space \ station in the vicinity) BMI WA2 \ If the sun's z_sign is negative, then the sun is \ behind us, so jump to WA2 to skip the following LDY #NI% \ Set Y to point to the offset of the ship data block \ for the sun, which is NI% (as each block is NI% bytes \ long, and the sun is the second block) JSR m \ Call m to set A to the largest distance to the sun \ in any of the three axes \ The following two instructions appear in the BASIC \ source file (ELITEC), but in the text source file \ (ELITEC.TXT) they are replaced by: \ \ LSR A \ BEQ WA1 \ \ which does the same thing, but saves one byte of \ memory (as LSR A is a one-byte opcode, while CMP #2 \ takes up two bytes) CMP #2 \ If A < 2 then jump to WA1 to abort the in-system jump BCC WA1 \ with a low beep, as we are facing the sun and are too \ close to jump in that direction .WA2 \ If we get here, then we can do an in-system jump, as \ we don't have any ships or space stations in the \ vicinity, we are not in witchspace, and if we are \ facing the planet or the sun, we aren't too close to \ jump towards it \ \ We do an in-system jump by moving the sun and planet, \ rather than moving our own local bubble (this is why \ in-system jumps drag asteroids, cargo canisters and \ escape pods along for the ride). Specifically, we move \ them in the z-axis by a fixed amount in the opposite \ direction to travel, thus performing a jump towards \ our destination LDA #&81 \ Set R = R = P = &81 STA S STA R STA P LDA K%+8 \ Set A = z_sign for the planet JSR ADD \ Set (A X) = (A P) + (S R) \ = (z_sign &81) + &8181 \ = (z_sign &81) - &0181 \ \ This moves the planet against the direction of travel \ by reducing z_sign by 1, as the above maths is: \ \ z_sign 00000000 \ + 00000000 10000001 \ - 00000001 10000001 \ \ or: \ \ z_sign 00000000 \ + 00000000 00000000 \ - 00000001 00000000 \ \ i.e. the high byte is z_sign - 1, making sure the sign \ is preserved STA K%+8 \ Set the planet's z_sign to the high byte of the result LDA K%+NI%+8 \ Set A = z_sign for the sun JSR ADD \ Set (A X) = (A P) + (S R) \ = (z_sign &81) + &8181 \ = (z_sign &81) - &0181 \ \ which moves the sun against the direction of travel \ by reducing z_sign by 1 STA K%+NI%+8 \ Set the planet's z_sign to the high byte of the result LDA #1 \ These instructions have no effect, as the call to STA QQ11 \ LOOK1 below starts by setting QQ11 to 0; instead they \ just set the current view type in QQ11 to 1 for the \ duration of the next three instructions STA MCNT \ Set the main loop counter to 1, so the next iteration \ through the main loop will potentially spawn ships \ (see part 2 of the main game loop at me3) LSR A \ Set EV, the extra vessels spawning counter, to 0 STA EV \ (the LSR produces a 0 as A was previously 1) LDX VIEW \ Set X to the current view (front, rear, left or right) JMP LOOK1 \ and jump to LOOK1 to initialise that view, returning \ from the subroutine with a tail call .WA1 LDA #40 \ If we get here then we can't do an in-system jump, so JMP NOISE \ call the NOISE routine with A = 40 to make a long, low \ beep and return from the subroutine using a tail call }
Name: LASLI [View individually] Type: Subroutine Category: Drawing lines Summary: Draw the laser lines for when we fire our lasers
Draw the laser lines, aiming them to slightly different place each time so they appear to flicker and dance. Also heat up the laser temperature and drain some energy. Other entry points: LASLI2 Just draw the current laser lines without moving the centre point, draining energy or heating up. This has the effect of removing the lines from the screen
.LASLI { JSR DORND \ Set A and X to random numbers AND #7 \ Restrict A to a random value in the range 0 to 7 ADC #Y-4 \ Set LASY to four pixels above the centre of the STA LASY \ screen (#Y), plus our random number, so the laser \ dances above and below the centre point JSR DORND \ Set A and X to random numbers AND #7 \ Restrict A to a random value in the range 0 to 7 ADC #X-4 \ Set LASX to four pixels left of the centre of the STA LASX \ screen (#X), plus our random number, so the laser \ dances to the left and right of the centre point LDA GNTMP \ Add 8 to the laser temperature in GNTMP ADC #8 STA GNTMP JSR DENGY \ Call DENGY to deplete our energy banks by 1 .^LASLI2 LDA QQ11 \ If this is not a space view (i.e. QQ11 is non-zero) BNE PU1-1 \ then jump to MA9 to return from the main flight loop \ (as PU1-1 is an RTS) LDA #32 \ Call las with A = 32 and Y = 224 to draw one set of LDY #224 \ laser lines JSR las LDA #48 \ Fall through into las with A = 48 and Y = 208 to draw LDY #208 \ a second set of lines \ The following routine draws two laser lines, one from \ the centre point down to point A on the bottom row, \ and the other from the centre point down to point Y \ on the bottom row. We therefore get lines from the \ centre point to points 32, 48, 208 and 224 along the \ bottom row, giving us the triangular laser effect \ we're after .las STA X2 \ Set X2 = A LDA LASX \ Set (X1, Y1) to the random centre point we set above STA X1 LDA LASY STA Y1 LDA #2*Y-1 \ Set Y2 = 2 * #Y - 1. The constant #Y is 96, the STA Y2 \ y-coordinate of the mid-point of the space view, so \ this sets Y2 to 191, the y-coordinate of the bottom \ pixel row of the space view JSR LOIN \ Draw a line from (X1, Y1) to (X2, Y2), so that's from \ the centre point to (A, 191) LDA LASX \ Set (X1, Y1) to the random centre point we set above STA X1 LDA LASY STA Y1 STY X2 \ Set X2 = Y LDA #2*Y-1 \ Set Y2 = 2 * #Y - 1, the y-coordinate of the bottom STA Y2 \ pixel row of the space view (as before) JMP LOIN \ Draw a line from (X1, Y1) to (X2, Y2), so that's from \ the centre point to (Y, 191), and return from \ the subroutine using a tail call }
Name: PLUT [View individually] Type: Subroutine Category: Flight Summary: Flip the coordinate axes for the four different views
This routine flips the relevant geometric axes in INWK depending on which view we are looking through (front, rear, left, right). Other entry points: LO2 Contains an RTS PU1-1 Contains an RTS
.PLUT { LDX VIEW \ Load the current view into X: \ \ 0 = front \ 1 = rear \ 2 = left \ 3 = right BNE PU1 \ If the current view is the front view, return from the RTS \ subroutine, as the geometry in INWK is already correct .^PU1 DEX \ Decrement the view, so now: \ \ 0 = rear \ 1 = left \ 2 = right BNE PU2 \ If the current view is left or right, jump to PU2, \ otherwise this is the rear view, so continue on LDA INWK+2 \ Flip the sign of x_sign EOR #%10000000 STA INWK+2 LDA INWK+8 \ Flip the sign of z_sign EOR #%10000000 STA INWK+8 LDA INWK+10 \ Flip the sign of nosev_x_hi EOR #%10000000 STA INWK+10 LDA INWK+14 \ Flip the sign of nosev_z_hi EOR #%10000000 STA INWK+14 LDA INWK+16 \ Flip the sign of roofv_x_hi EOR #%10000000 STA INWK+16 LDA INWK+20 \ Flip the sign of roofv_z_hi EOR #%10000000 STA INWK+20 LDA INWK+22 \ Flip the sign of sidev_x_hi EOR #%10000000 STA INWK+22 LDA INWK+26 \ Flip the sign of roofv_z_hi EOR #%10000000 STA INWK+26 RTS \ Return from the subroutine .PU2 \ We enter this with X set to the view, as follows: \ \ 1 = left \ 2 = right LDA #0 \ Set RAT2 = 0 (left view) or -1 (right view) CPX #2 ROR A STA RAT2 EOR #%10000000 \ Set RAT = -1 (left view) or 0 (right view) STA RAT LDA INWK \ Swap x_lo and z_lo LDX INWK+6 STA INWK+6 STX INWK LDA INWK+1 \ Swap x_hi and z_hi LDX INWK+7 STA INWK+7 STX INWK+1 LDA INWK+2 \ Swap x_sign and z_sign EOR RAT \ If left view, flip sign of new z_sign TAX \ If right view, flip sign of new x_sign LDA INWK+8 EOR RAT2 STA INWK+2 STX INWK+8 LDY #9 \ Swap nosev_x_lo and nosev_z_lo JSR PUS1 \ Swap nosev_x_hi and nosev_z_hi \ If left view, flip sign of new nosev_z_hi \ If right view, flip sign of new nosev_x_hi LDY #15 \ Swap roofv_x_lo and roofv_z_lo JSR PUS1 \ Swap roofv_x_hi and roofv_z_hi \ If left view, flip sign of new roofv_z_hi \ If right view, flip sign of new roofv_x_hi LDY #21 \ Swap sidev_x_lo and sidev_z_lo \ Swap sidev_x_hi and sidev_z_hi \ If left view, flip sign of new sidev_z_hi \ If right view, flip sign of new sidev_x_hi .PUS1 LDA INWK,Y \ Swap the low x and z bytes for the vector in Y: LDX INWK+4,Y \ STA INWK+4,Y \ * For Y = 9 swap nosev_x_lo and nosev_z_lo STX INWK,Y \ * For Y = 15 swap roofv_x_lo and roofv_z_lo \ * For Y = 21 swap sidev_x_lo and sidev_z_lo LDA INWK+1,Y \ Swap the high x and z bytes for the offset in Y: EOR RAT \ TAX \ * If left view, flip sign of new z-coordinate LDA INWK+5,Y \ * If right view, flip sign of new x-coordinate EOR RAT2 STA INWK+1,Y STX INWK+5,Y \ Fall through into LOOK1 to return from the subroutine }
Name: LOOK1 [View individually] Type: Subroutine Category: Flight Summary: Initialise the space view
Initialise the space view, with the direction of view given in X. This clears the upper screen and draws the laser crosshairs, if the view in X has lasers fitted. It also wipes all the ships from the scanner, so we can recalculate ship positions for the new view (they get put back in the main flight loop). Arguments: X The space view to set: * 0 = front * 1 = rear * 2 = left * 3 = right
{ .LO2 RTS \ Return from the subroutine .LQ STX VIEW \ Set the current space view to X JSR TT66 \ Clear the top part of the screen, draw a white border, \ and set the current view type in QQ11 to 0 (space \ view) JSR SIGHT \ Draw the laser crosshairs JMP NWSTARS \ Set up a new stardust field and return from the \ subroutine using a tail call .^LOOK1 LDA #0 \ Set A = 0, the type number of a space view LDY QQ11 \ If the current view is not a space view, jump up to LQ BNE LQ \ to set up a new space view CPX VIEW \ If the current view is already of type X, jump to LO2 BEQ LO2 \ to return from the subroutine (as LO2 contains an RTS) STX VIEW \ Change the current space view to X JSR TT66 \ Clear the top part of the screen, draw a white border, \ and set the current view type in QQ11 to 0 (space \ view) JSR FLIP \ Swap the x- and y-coordinates of all the stardust \ particles JSR WPSHPS \ Wipe all the ships from the scanner \ And fall through into SIGHT to draw the laser \ crosshairs .SIGHT LDY VIEW \ Fetch the laser power for our new view, and if it is LDA LASER,Y \ zero (i.e. there is no laser fitted to this view), BEQ LO2 \ jump to LO2 to return from the subroutine (as LO2 \ contains an RTS) LDA #128 \ Set QQ19 to the x-coordinate of the centre of the STA QQ19 \ screen LDA #Y-24 \ Set QQ19+1 to the y-coordinate of the centre of the STA QQ19+1 \ screen, minus 24 (because TT15 will add 24 to the \ coordinate when it draws the crosshairs) LDA #20 \ Set QQ19+2 to size 20 for the crosshairs size STA QQ19+2 JSR TT15 \ Call TT15 to draw crosshairs of size 20 just to the \ left of the middle of the screen LDA #10 \ Set QQ19+2 to size 10 for the crosshairs size STA QQ19+2 JMP TT15 \ Call TT15 to draw crosshairs of size 10 at the same \ location, which will remove the centre part from the \ laser crosshairs, leaving a gap in the middle, and \ return from the subroutine using a tail call }
Name: TT66 [View individually] Type: Subroutine Category: Utility routines Summary: Clear the screen and set the current view type
Clear the top part of the screen (mode 4), draw a white border, and set the current view type in QQ11 to A. Arguments: A The type of the new current view (see QQ11 for a list of view types) Other entry points: TT66-2 Call TT66 with A = 1
{ LDA #1 .^TT66 STA QQ11 \ Set the current view type in QQ11 to A \ Fall through into TTX66 to clear the screen and draw a \ white border }
Name: TTX66 [View individually] Type: Subroutine Category: Utility routines Summary: Clear the top part of the screen and draw a white border
Clear the top part of the screen (the mode 4 part) and draw a white border along the top and sides. Other entry points: BOX Just draw the border and (if this is a space view) the view name. This can be used to remove the border and view name, as it is drawn using EOR logic
.TTX66 { LDA #%10000000 \ Set bit 7 of QQ17 to switch to Sentence Case STA QQ17 ASL A \ Set LASCT to 0, as 128 << 1 = %10000000 << 1 = 0. This STA LASCT \ stops any laser pulsing. This instruction is STA LS2 \ in the text source file ELITEC.TXT STA DLY \ Set the delay in DLY to 0, to indicate that we are \ no longer showing an in-flight message, so any new \ in-flight messages will be shown instantly STA de \ Clear de, the flag that appends " DESTROYED" to the \ end of the next text token, so that it doesn't LDX #&60 \ Set X to the screen memory page for the top row of the \ screen (as screen memory starts at &6000) .BOL1 JSR ZES1 \ Call ZES1 below to zero-fill the page in X, which \ clears that character row on the screen INX \ Increment X to point to the next page, i.e. the next \ character row CPX #&78 \ Loop back to BOL1 until we have cleared page &7700, BNE BOL1 \ the last character row in the space view part of the \ screen (the mode 4 part) LDX QQ22+1 \ Fetch into X the number that's shown on-screen during \ the hyperspace countdown BEQ BOX \ If the counter is zero then we are not counting down \ to hyperspace, so jump to BOX to skip the next \ instruction JSR ee3 \ Print the 8-bit number in X at text location (0, 1), \ i.e. print the hyperspace countdown in the top-left \ corner .^BOX LDY #1 \ Move the text cursor to row 1 STY YC LDA QQ11 \ If this is not a space view, jump to tt66 to skip BNE tt66 \ displaying the view name LDY #11 \ Move the text cursor to row 11 STY XC LDA VIEW \ Load the current view into A: \ \ 0 = front \ 1 = rear \ 2 = left \ 3 = right ORA #&60 \ OR with &60 so we get a value of &60 to &63 (96 to 99) JSR TT27 \ Print recursive token 96 to 99, which will be in the \ range "FRONT" to "RIGHT" JSR TT162 \ Print a space LDA #175 \ Print recursive token 15 ("VIEW ") JSR TT27 .tt66 LDX #0 \ Set (X1, Y1) to (0, 0) STX X1 STX Y1 STX QQ17 \ Set QQ17 = 0 to switch to ALL CAPS DEX \ Set X2 = 255 STX X2 JSR HLOIN \ Draw a horizontal line from (X1, Y1) to (X2, Y1), so \ that's (0, 0) to (255, 0), along the very top of the \ screen LDA #2 \ Set X1 = X2 = 2 STA X1 STA X2 JSR BOS2 \ Call BOS2 below, which will call BOS1 twice, and then \ fall through into BOS2 again, so we effectively do \ BOS1 four times, decrementing X1 and X2 each time \ before calling LOIN, so this whole loop-within-a-loop \ mind-bender ends up drawing these four lines: \ \ (1, 0) to (1, 191) \ (0, 0) to (0, 191) \ (255, 0) to (255, 191) \ (254, 0) to (254, 191) \ \ So that's a 2-pixel wide vertical border along the \ left edge of the upper, mode 4 part of the screen, and \ a 2-pixel wide vertical border along the right edge .BOS2 JSR BOS1 \ Call BOS1 below and then fall through into it, which \ ends up running BOS1 twice. This is all part of the \ loop-the-loop border-drawing mind-bender explained \ above .BOS1 LDA #0 \ Set Y1 = 0 STA Y1 LDA #2*Y-1 \ Set Y2 = 2 * #Y - 1. The constant #Y is 96, the STA Y2 \ y-coordinate of the mid-point of the space view, so \ this sets Y2 to 191, the y-coordinate of the bottom \ pixel row of the space view DEC X1 \ Decrement X1 and X2 DEC X2 JMP LOIN \ Draw a line from (X1, Y1) to (X2, Y2), and return from \ the subroutine using a tail call }
Name: DELAY [View individually] Type: Subroutine Category: Utility routines Summary: Wait for a specified time, in 1/50s of a second
Wait for the number of vertical syncs given in Y, so this effectively waits for Y/50 of a second (as the vertical sync occurs 50 times a second). Arguments: Y The number of vertical sync events to wait for Other entry points: DEL8 Wait for 8/50 of a second (0.16 seconds) DELAY-5 Wait for 2/50 of a second (0.04 seconds).
{ LDY #2 \ Set Y to 2 vertical syncs EQUB &2C \ Skip the next instruction by turning it into \ &2C &A0 &08, or BIT &08A0, which does nothing bar \ affecting the flags .^DEL8 LDY #8 \ Set Y to 8 vertical syncs and fall through into DELAY \ to wait for this long .^DELAY JSR WSCAN \ Call WSCAN to wait for the vertical sync, so the whole \ screen gets drawn DEY \ Decrement the counter in Y BNE DELAY \ If Y isn't yet at zero, jump back to DELAY to wait \ for another vertical sync RTS \ Return from the subroutine }
Name: hm [View individually] Type: Subroutine Category: Charts Summary: Select the closest system and redraw the chart crosshairs
Set the system closest to galactic coordinates (QQ9, QQ10) as the selected system, redraw the crosshairs on the chart accordingly (if they are being shown), and, if this is not a space view, clear the bottom three text rows of the screen.
.hm { JSR TT103 \ Draw small crosshairs at coordinates (QQ9, QQ10), \ which will erase the crosshairs currently there JSR TT111 \ Select the system closest to galactic coordinates \ (QQ9, QQ10) JSR TT103 \ Draw small crosshairs at coordinates (QQ9, QQ10), \ which will draw the crosshairs at our current home \ system LDA QQ11 \ If this is a space view, return from the subroutine BEQ SC5 \ (as SC5 contains an RTS) \ Otherwise fall through into CLYNS to clear space at \ the bottom of the screen }
Name: CLYNS [View individually] Type: Subroutine Category: Utility routines Summary: Clear the bottom three text rows of the mode 4 screen
Clear some space at the bottom of the screen and move the text cursor to column 1, row 21. Specifically, this zeroes the following screen locations: &7507 to &75F0 &7607 to &76F0 &7707 to &77F0 which clears the three bottom text rows of the mode 4 screen (rows 21 to 23), clearing each row from text column 1 to 30 (so it doesn't overwrite the box border in columns 0 and 32, or the last usable column in column 31). Returns: A A is set to 0 Y Y is set to 0
.CLYNS { LDA #20 \ Move the text cursor to row 20, near the bottom of STA YC \ the screen LDA #&75 \ Set the two-byte value in SC to &7507 STA SC+1 LDA #7 STA SC JSR TT67 \ Print a newline, which will move the text cursor down \ a line (to row 21) and back to column 1 LDA #0 \ Call LYN to clear the pixels from &7507 to &75F0 JSR LYN INC SC+1 \ Increment SC+1 so SC points to &7607 JSR LYN \ Call LYN to clear the pixels from &7607 to &76F0 INC SC+1 \ Increment SC+1 so SC points to &7707 INY \ Move the text cursor to column 1 (as LYN sets Y to 0) STY XC \ Fall through into LYN to clear the pixels from &7707 \ to &77F0 }
Name: LYN [View individually] Type: Subroutine Category: Utility routines Summary: Clear most of a row of pixels
Set pixels 0-233 to the value in A, starting at the pixel pointed to by SC. Arguments: A The value to store in pixels 1-233 (the only value that is actually used is A = 0, which clears those pixels) Returns: Y Y is set to 0 Other entry points: SC5 Contains an RTS
.LYN { LDY #233 \ Set up a counter in Y to count down from pixel 233 .EE2 STA (SC),Y \ Store A in the Y-th byte after the address pointed to \ by SC DEY \ Decrement Y BNE EE2 \ Loop back until Y is zero .^SC5 RTS \ Return from the subroutine }
Name: SCAN [View individually] Type: Subroutine Category: Dashboard Summary: Display the current ship on the scanner
This is used both to display a ship on the scanner, and to erase it again. Arguments: INWK The ship's data block
.SCAN { LDA INWK+31 \ Fetch the ship's scanner flag from byte #31 AND #%00010000 \ If bit 4 is clear then the ship should not be shown BEQ SC5 \ on the scanner, so return from the subroutine (as SC5 \ contains an RTS) LDA TYPE \ Fetch the ship's type from TYPE into A BMI SC5 \ 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 return from the subroutine (as SC5 \ contains an RTS) LDX #&FF \ Set X to the default scanner colour of green/cyan \ (a 4-pixel mode 5 byte in colour 3) \CMP #TGL \ These instructions are commented out in the original \BEQ SC49 \ source. Along with the block just below, they would \ set X to colour 1 (red) for asteroids, cargo canisters \ and escape pods, rather than green/cyan. Presumably \ they decided it didn't work that well against the red \ ellipse and took this code out for release CMP #MSL \ If this is not a missile, skip the following BNE P%+4 \ instruction LDX #&F0 \ This is a missile, so set X to colour 2 (yellow/white) \CMP #AST \ These instructions are commented out in the original \BCC P%+4 \ source. See above for an explanation of what they do \LDX #&0F \.SC49 STX COL \ Store X, the colour of this ship on the scanner, in \ COL LDA INWK+1 \ If any of x_hi, y_hi and z_hi have a 1 in bit 6 or 7, ORA INWK+4 \ then the ship is too far away to be shown on the ORA INWK+7 \ scanner, so return from the subroutine (as SC5 AND #%11000000 \ contains an RTS) BNE SC5 \ If we get here, we know x_hi, y_hi and z_hi are all \ 63 (%00111111) or less \ Now, we convert the x_hi coordinate of the ship into \ the screen x-coordinate of the dot on the scanner, \ using the following (see above for an explanation): \ \ X1 = 123 + (x_sign x_hi) LDA INWK+1 \ Set x_hi CLC \ Clear the C flag so we can do addition below LDX INWK+2 \ Set X = x_sign BPL SC2 \ If x_sign is positive, skip the following EOR #%11111111 \ x_sign is negative, so flip the bits in A and subtract ADC #1 \ 1 to make it a negative number (bit 7 will now be set \ as we confirmed above that bits 6 and 7 are clear). So \ this gives A the sign of x_sign and gives it a value \ range of -63 (%11000001) to 0 .SC2 ADC #123 \ Set X1 = 123 + x_hi STA X1 \ Next, we convert the z_hi coordinate of the ship into \ the y-coordinate of the base of the ship's stick, \ like this (see above for an explanation): \ \ SC = 220 - (z_sign z_hi) / 4 \ \ though the following code actually does it like this: \ \ SC = 255 - (35 + z_hi / 4) LDA INWK+7 \ Set A = z_hi / 4 LSR A \ LSR A \ So A is in the range 0-15 CLC \ Clear the C flag LDX INWK+8 \ Set X = z_sign BPL SC3 \ If z_sign is positive, skip the following EOR #%11111111 \ z_sign is negative, so flip the bits in A and set the SEC \ C flag. As above, this makes A negative, this time \ with a range of -16 (%11110000) to -1 (%11111111). And \ as we are about to do an ADC, the SEC effectively adds \ another 1 to that value, giving a range of -15 to 0 .SC3 ADC #35 \ Set A = 35 + A to give a number in the range 20 to 50 EOR #%11111111 \ Flip all the bits and store in SC, so SC is in the STA SC \ range 205 to 235, with a higher z_hi giving a lower SC \ Now for the stick height, which we calculate using the \ following (see above for an explanation): \ \ A = - (y_sign y_hi) / 2 LDA INWK+4 \ Set A = y_hi / 2 LSR A CLC \ Clear the C flag LDX INWK+5 \ Set X = y_sign BMI SCD6 \ If y_sign is negative, skip the following, as we \ already have a positive value in A EOR #%11111111 \ y_sign is positive, so flip the bits in A and set the SEC \ C flag. This makes A negative, and as we are about to \ do an ADC below, the SEC effectively adds another 1 to \ that value to implement two's complement negation, so \ we don't need to add another 1 here .SCD6 \ We now have all the information we need to draw this \ ship on the scanner, namely: \ \ X1 = the screen x-coordinate of the ship's dot \ \ SC = the screen y-coordinate of the base of the \ stick \ \ A = the screen height of the ship's stick, with the \ correct sign for adding to the base of the stick \ to get the dot's y-coordinate \ \ First, though, we have to make sure the dot is inside \ the dashboard, by moving it if necessary ADC SC \ Set A = SC + A, so A now contains the y-coordinate of \ the end of the stick, plus the length of the stick, to \ give us the screen y-coordinate of the dot BPL ld246 \ If the result has bit 0 clear, then the result has \ overflowed and is bigger than 256, so jump to ld246 to \ set A to the maximum allowed value of 246 (this \ instruction isn't required as we test both the maximum \ and minimum below, but it might save a few cycles) CMP #194 \ If A >= 194, skip the following instruction, as 194 is BCS P%+4 \ the minimum allowed value of A LDA #194 \ A < 194, so set A to 194, the minimum allowed value \ for the y-coordinate of our ship's dot CMP #247 \ If A < 247, skip the following instruction, as 246 is BCC P%+4 \ the maximum allowed value of A .ld246 LDA #246 \ A >= 247, so set A to 246, the maximum allowed value \ for the y-coordinate of our ship's dot STA Y1 \ Store A in Y1, as it now contains the screen \ y-coordinate for the ship's dot, clipped so that it \ fits within the dashboard SEC \ Set A = A - SC to get the stick length, by reversing SBC SC \ the ADC SC we did above. This clears the C flag if the \ result is negative (i.e. the stick length is negative) \ and sets it if the result is positive (i.e. the stick \ length is negative) \ So now we have the following: \ \ X1 = the screen x-coordinate of the ship's dot, \ clipped to fit into the dashboard \ \ Y1 = the screen y-coordinate of the ship's dot, \ clipped to fit into the dashboard \ \ SC = the screen y-coordinate of the base of the \ stick \ \ A = the screen height of the ship's stick, with the \ correct sign for adding to the base of the stick \ to get the dot's y-coordinate \ \ C = 0 if A is negative, 1 if A is positive \ \ and we can get on with drawing the dot and stick PHP \ Store the flags (specifically the C flag) from the \ above subtraction \BCS SC48 \ These instructions are commented out in the original \EOR #&FF \ source. They would negate A if the C flag were set, \ADC #1 \ which would reverse the direction of all the sticks, \ so you could turn your joystick around. Perhaps one of \ the authors' test sticks was easier to use upside \ down? Who knows... .SC48 PHA \ Store the stick height in A on the stack JSR CPIX4 \ Draw a double-height mode 5 dot at (X1, Y1). This also \ leaves the following variables set up for the dot's \ top-right pixel, the last pixel to be drawn (as the \ dot gets drawn from the bottom up): \ \ SC(1 0) = screen address of the pixel's character \ block \ \ Y = number of the character row containing the pixel \ \ X = the pixel's number (0-3) in that row \ \ We can use there as the starting point for drawing the \ stick, if there is one LDA CTWOS+1,X \ Load the same mode 5 1-pixel byte that we just used AND COL \ for the top-right pixel, and mask it with the same STA X1 \ colour, storing the result in X1, so we can use it as \ the character row byte for the stick PLA \ Restore the stick height from the stack into A PLP \ Restore the flags from above, so the C flag once again \ reflects the sign of the stick height TAX \ Copy the stick height into X BEQ RTS \ If the stick height is zero, then there is no stick to \ draw, so return from the subroutine (as RTS contains \ an RTS) BCC RTS+1 \ If the C flag is clear then the stick height in A is \ negative, so jump down to RTS+1 .VLL1 \ If we get here then the stick length is positive (so \ the dot is below the ellipse and the stick is above \ the dot, and we need to draw the stick upwards from \ the dot) DEY \ We want to draw the stick upwards, so decrement the \ pixel row in Y BPL VL1 \ If Y is still positive then it correctly points at the \ line above, so jump to VL1 to skip the following LDY #7 \ We just decremented Y up through the top of the \ character block, so we need to move it to the last row \ in the character above, so set Y to 7, the number of \ the last row DEC SC+1 \ Decrement the high byte of the screen address to move \ to the character block above .VL1 LDA X1 \ Set A to the character row byte for the stick, which \ we stored in X1 above, and which has the same pixel \ pattern as the bottom-right pixel of the dot (so the \ stick comes out of the right side of the dot) EOR (SC),Y \ Draw the stick on row Y of the character block using STA (SC),Y DEX \ Decrement (positive) the stick height in X BNE VLL1 \ If we still have more stick to draw, jump up to VLL1 \ to draw the next pixel .RTS RTS \ Return from the subroutine \ If we get here then the stick length is negative (so \ the dot is above the ellipse and the stick is below \ the dot, and we need to draw the stick downwards from \ the dot) INY \ We want to draw the stick downwards, so we first \ increment the row counter so that it's pointing to the \ bottom-right pixel in the dot (as opposed to the top- \ right pixel that the call to CPIX2 finished on) CPY #8 \ If the row number in Y is less than 8, then it BNE P%+6 \ correctly points at the next line down, so jump to \ VLL2 to skip the following LDY #0 \ We just incremented Y down through the bottom of the \ character block, so we need to move it to the first \ row in the character below, so set Y to 0, the number \ of the first row INC SC+1 \ Increment the high byte of the screen address to move \ to the character block above .VLL2 INY \ We want to draw the stick itself, heading downwards, \ so increment the pixel row in Y CPY #8 \ If the row number in Y is less than 8, then it BNE VL2 \ correctly points at the next line down, so jump to \ VL2 to skip the following LDY #0 \ We just incremented Y down through the bottom of the \ character block, so we need to move it to the first \ row in the character below, so set Y to 0, the number \ of the first row INC SC+1 \ Increment the high byte of the screen address to move \ to the character block above .VL2 LDA X1 \ Set A to the character row byte for the stick, which \ we stored in X1 above, and which has the same pixel \ pattern as the bottom-right pixel of the dot (so the \ stick comes out of the right side of the dot) EOR (SC),Y \ Draw the stick on row Y of the character block using STA (SC),Y INX \ Decrement the (negative) stick height in X BNE VLL2 \ If we still have more stick to draw, jump up to VLL2 \ to draw the next pixel RTS \ Return from the subroutine }
Name: WSCAN [View individually] Type: Subroutine Category: Screen mode Summary: Wait for the vertical sync
Wait for vertical sync to occur on the video system - in other words, wait for the screen to start its refresh cycle, which it does 50 times a second (50Hz).
.WSCAN { LDA #0 \ Set DL to 0 STA DL LDA DL \ Loop round these two instructions until DL is no BEQ P%-2 \ longer 0 (DL gets set to 30 in the LINSCN routine, \ which is run when vertical sync has occurred on the \ video system, so DL will change to a non-zero value \ at the start of each screen refresh) RTS \ Return from the subroutine }
Save output/ELTC.bin
PRINT "ELITE C" PRINT "Assembled at ", ~CODE_C% PRINT "Ends at ", ~P% PRINT "Code size is ", ~(P% - CODE_C%) PRINT "Execute at ", ~LOAD% PRINT "Reload at ", ~LOAD_C% PRINT "S.ELTC ", ~CODE_C%, " ", ~P%, " ", ~LOAD%, " ", ~LOAD_C% SAVE "output/ELTC.bin", CODE_C%, P%, LOAD%