Lobsters retrocomputing - 03 Sep 2026

Page 5 of 5

This jumping around only happens in player 2's view, due to inaccuracies in the translation and rotation transformation we explored in the geometry behind player 2's view. So there's a hack in part 2 of DrawPlayer2View that checks whether the planet is currently on-screen in player 2's view, and if it is then we skip the usual application of the two-part transformation, and instead we only move it by the rotations of player 2's controls. This means that the planet doesn't jump around on-screen and instead moves smoothly with the pitch and roll of the player.

Once the planet moves off-screen, we switch back to applying the full two-part transformation, so that it snaps back to the correct position in space. This realignment isn't seen by the player as it's only done when the planet is off-screen, but it can mean that if the planet moves off-screen and then back on-screen quite quickly, it can sometimes appear to have jumped to a new position. Luckily this isn't obvious in fast-paced combat, but if you treat two-player Elite as a gentle stroll through deep space with a friend, then it might be a bit more obvious that something strange is going on behind the curtain.

On top of this cheat, there's another tweak to the normal flow that reduces the amount of on-screen strangeness. Because of the various mathematical approximations used in the ship rotation routines in Elite, and in particular the small angle approximation, we have to "tidy" each ship's orientation vectors periodically to ensure they remain orthonormal; see the deep dive on tidying orthonormal vectors for details.

In normal single-player Elite this isn't too noticeable, because tidying a ship's orientation vectors doesn't change its coordinates in space, it just stretches the ship's shape back into the correct dimensions. As a result, the most you will see is a bit of a wobble in the shape of the ship, which is very hard to see, particularly if the ship is moving.

But in two-player Elite, the orientation vectors are at the core of the two-step transformation process that we use to draw player 2's view, so changing an orientation vector will affect both the coordinates and the orientation of the on-screen ship within player 2's view. As a result, tidying a ship's vectors while it's in player 2's view can make it jump around very noticeably, particularly if the vectors have degraded a long way from being orthonormal. So two-player Elite extends the on-screen checks to the tidying process, so we only tidy the vectors for the planet, the sun and player 1's ship if it isn't being drawn in player 2's view. For missiles, however, we can get away with tidying as we see fit, as they move like the clappers and have a short lifespan, so the chances of vector degradation is low.

This tidying hack has a downside. If you keep the planet, sun or opponent in player 2's view for an extended amount of time, then their orientation vectors will never get tidied and they will start to degrade over time, so player 1's ship will slowly deform, and the planet's circles will get all twisted. But again this is very unlikely to happen during one-on-one combat, so the risk is well worth taking.

Responsive controls for two players

-----------------------------------

Elite has sophisticated support for processing multiple keypresses. It has a key logger that has a number of slots, into which the game records key presses for seven primary controls (pitch, roll, speed and lasers), nine secondary controls (missiles, E.C.M., in-system jump and so on), and one other arbitrary key press. The key logger is refreshed on every iteration of the main loop by scanning the keyboard for the relevant controls and populating the logger, and it enables the game to support a number of keys being pressed at the same time, which is essential in a fast-paced game like Elite. This system is described in more detail in the deep dive on the key logger.

Not surprisingly, the key logger is only designed for one player. If we try to use the original key logger with two players, then the only slot available to the second player is the arbitrary key press slot, and that gets populated by a keyboard scan that stops as soon as it has found a key, irrespective of whether it's being pressed by player 1 or player 2. Obviously, this isn't anywhere near a solution for a two-player game where we need to give both players the same level of control.

Luckily it isn't too difficult to extend the key logger, so two-player Elite adds two more key slots. This gives us enough slots to cover both the primary and secondary controls for the two players, as we don't need to support the full set of key presses from the original game; for example, we can repurpose existing key slots like the energy bomb and in-system jump for player 2's flight controls.

This gives the primary and secondary flight controls for each player the same level of support, so both players can fly their ships and shoot their missiles and lasers without being affected by the other player's actions. But there are a few other controls that we need to support in the two-player version, such as the keys to change between the front, rear, left and right views, and therein lies the problem.

The key logger uses the single-byte "other key press" entry for all these other controls, and it populates this with a scan of the keyboard. This scan works through the keyboard from low internal key numbers to high, and when it detects a key press, it stops and logs that as the "other key". This is fine for one player, but the problem with two players is that if a player holds down a key that appears early on in the full-keyboard scan (i.e. a key with a low internal key number), then that key will fill the "other key press" slot, the keyboard scanning will stop, and any other key presses will be ignored. This means that if player 1 holds down the f0 key to switch to their front view, then because f0 has internal key number &20, this will disable all of player 2's keys that have a higher internal key number. This turns out to be all of them except for the left arrow key, which has an internal key number of &19, so all player 1 has to do to disable a bunch of player 2's keys is to hold down f0. This clearly will not do.

The same issue affects the buttons on the Delta 14B joystick, which we scan in the same keyboard routine when Delta 14B sticks are configured:

This is because it doesn't matter whether we're talking about keys on a keyboard or buttons on a joystick - the problem is that these extra controls get squashed into just one byte in the logger.

One solution would be to add a second "other key" byte so there's one for each player; we could then reserve one byte for player 1 and the other for player 2, and make sure the keyboard scan only stops early if keys have been detected for both players. It turns out that this approach would need a fair bit of recoding in the parasite code and wouldn't be the fastest solution, so instead I've added a very simple hack to "timeshare" the extra keys between the two players.

The I/O processor, which does the keyboard scanning, contains a new flag variable called player2Turn that flips bit 7 every time the I/O processor is asked to scan the keyboard. When bit 7 is clear, player 1 has precedence, so we make sure we return a player 1 key in the arbitrary key slot, if one is being pressed; and when bit 7 is set, player 2 has precedence, so we make sure we return a player 2 key, if one is being pressed. If the player with precedence is not pressing a key, then we can return the other player's key press, if there is one.

In this way we can stick to the one-byte "other key" slot, and all we need to do is make sure we only abort the keyboard scan early if we find a key press that matches the player with precedence. Because the keyboard is scanned very regularly, this simple system constantly flips precedence between the two players, so neither of them will notice that only one of them has precedence at any one time.

The result is a properly responsive keyboard that shouldn't cause too many arguments. I hope.

Miscellaneous

-------------

Here are some additional points that are worth noting about two-player Elite:

- The shared scanner is a simple consequence of the way DrawPlayer2View works. After the two-step transformation is performed, the current ship has the correct coordinates for drawing the ship in player 2's view... which means it also has the correct coordinates for drawing that ship on the scanner in the correct place for player 2 to use. So DrawPlayer2View calls the SCAN routine to update the ship on the scanner, and we can extend the call to the I/O processor to take an extra argument containing the colour, so player 1's scanner can contain cyan ships and player 2's scanner can contain yellow ships.

- On the subject of the scanner, we can also extend the call to the I/O processor to pass the ship type. This means that that missiles can be drawn with a thin dash at the end of the stick while other ship types are drawn with a thick dot.

- The scanner is zoomed-in by a factor of two compared to the single-player game, to make it easier to work out what's going on in close combat. This only requires a couple of shifts in the I/O processor's SC48 routine, which does the actual drawing.

- All the in-game text is implemented using the game's normal text token system. Two-player Elite disables the game's information screens, as we don't need things like market prices and system descriptions, so there are plenty of tokens that are suitable for conversion into two-player text.

- The main game loop and flight loops are considerably simpler than in the one-player game, as quite a lot of functionality is no longer required. The spawning code has been removed from the main game loop, so that's all of parts 1, 3, 4 and most of part 2 gone. And aspects like the energy bomb, docking, scooping and spawning are no longer needed in the main flight loop, so parts 5, 8, 9, 10 and 14 have been completely removed, as well as large chunks of parts 7, 11, 12, 13 and 15. Going in the other direction, the keyboard and joystick code in parts 2 and 3 of the main flight loop is more complicated, as it duplicates the primary flight control detection for the second player.

- On the subject of duplication, a number of player 1's routines have simply been duplicated for player 2; for example, player 2 has their own missile, laser, shield and energy routines, all of which copy the functionality of the one-player code. Here's a full list of duplicated routines, where a name like Player2XXX indicates that this duplicates the XXX routine from the original game:

- Player2ABORT

- Player2ABORT2

- Player2DENGY

- Player2ECBLB2

- Player2ECMOF

- Player2ee3

- Player2FR1

- Player2LASLI

- Player2LASLI2

- Player2me1

- Player2me2

- Player2me3

- Player2MESS

- Player2OOPS

- Player2SHD

- Player2SPS3

- To go along with these routines, there's also a collection of duplicated variables that are used to store things like player 2's energy levels and laser temperature. These variables can be found at the end of the WP workspace, where a name like player2XXX indicates that this duplicates the XXX variable from the original game. Variables at the start of the block, which appear between the startWP to endZero labels, get zeroed in the ZERO routine, so they contain values that need to be reset at the start of each game by the RESET or RES2 routines; variables after endZero either don't need resetting, or they contain configuration information from the main game screen, which we want to retain between games.

And that's two-player Elite. I hope you enjoy exploring it as much as I enjoyed writing it...

Previous page | More Lobsters retrocomputing | Headlines

Original: https://elite.bbcelite.com/hacks/two-player_elite/technical_information.html