Skip to navigation

The landscape secret code

Generating the eight-digit secret code for each landscape

In the deep dives on generating the landscape and adding enemies and trees to the landscape, we saw how The Sentinel takes a sequence of pseudo-random seed numbers and creates a whole landscape from them, including trees, sentries, the Sentinel and the player.

We're not quite done with the seed numbers, though, because now that we have generated and populated the landscape, we need to generate the landscape's secret code. We do this in two places:

  • If we are about to play a new landscape, then once the landscape has been generated and the enemies have been added, the preview screen is drawn, like this: The landscape preview screen for landscape 0000 in the BBC Micro version of The Sentinel Then the PreviewLandscape routine calls SpawnPlayer, which falls through into SpawnTrees, which falls through into CheckSecretCode. This latter routine then generates the secret code and checks it against the code that the player has entered; if they match then we start the game by jumping to PlayGame, otherwise we display the "WRONG SECRET CODE" error by jumping to SecretCodeError.
  • If the player has just beaten the Sentinel, then we need to display the secret code for the next landscape that's just been unlocked. To do that we call the GetNextLandscape routine to generate the new landscape and add the enemies, but this time we don't preview the landscape. Instead we call SpawnPlayer, which falls through into SpawnTrees, which falls through into CheckSecretCode to generate the secret code. This is then shown on the screen in large 3D digits, like this: The secret code screen in the BBC Micro version of The Sentinel

In both cases, the CheckSecretCode generates the landscape's secret code after the landscape has been generated and populated with enemies, trees and the player. Let's look at this process in more detail.

Generating the secret code
--------------------------

As noted above, by the time we call the CheckSecretCode routine, we have already generated the landscape and populated it with enemies, the player and trees, all using the landscape's sequence of seed numbers. This sequence will be different for each individual level, but it will be exactly the same sequence every time we generate a specific level.

We now keep generating seed numbers to get the landscape's secret code, as follows:

  • Generate another 38 numbers from the sequence. This doesn't appear to have any function other than to add yet more complexity to the process.
  • Generate the next four seed numbers in the sequence and use these to form the secret code.

To get a secret code of the form 12345678, we take the last four seed numbers and convert them into binary coded decimal (BCD) by using the GetNextSeedAsBCD routine. These four two-digit pairs then form the secret code, with each of the four numbers producing a pair of digits, building up the secret code from left to right (so that's in the order that they are written down).

So, to summarise, let's consider landscape 0002, whose secret code 88534263 is shown in the screenshot above. To generate this secret code for landscape number 0002, we have to do the following:

  • Seed the pseudo-random number generator's linear feedback shift register (LFSR) with the landscape number, i.e. &00 and &02.
  • Generate the landscape and add all the enemies, player and trees.
  • Generate 38 more seed numbers.
  • And then the next four seeds generated will be &88, &53, &42 and &63, which give us the secret code.

It's quite a process!

If we are displaying the landscape number on-screen at the end of a level, then the last four numbers are actually generated in the SpawnSecretCode3D routine rather than CheckSecretCode; if we are intending to play the landscape and are checking the secret code entered by the player, then we generate the last four numbers in the CheckSecretCode routine (and we generate one extra number that is ignored). This behaviour is controlled by the doNotPlayLandscape variable.

The landscape number, which is a two-byte BCD number, is used in a lot of places throughout The Sentinel's codebase, but there is only part bit of the whole game that actually uses the 6502's BCD mode. This bit of code is wrapped in SED and CLD instructions, to enable and disable BCD mode respectively, and it can be found at the start of the GetNextLandscape routine. This code calls GetPlayerEnergyBCD to fetch the player's energy in binary coded decimal format, and the remainder of the wrapped code simply adds the energy level to the landscape number to get the number of the next landscape. And that's it - all other code in The Sentinel uses normal, non-BCD mode.

Predicting the secret code
--------------------------

Calculating the secret code after we have generated and populated the whole landscape is a clever move, as it makes it practically impossible to work out the secret code for any given landscape without going through the whole landscape-generation process first. You might think that all we'd need to do would be to generate the correct number of seeds to arrive at the final step of generating the four secret code seeds, and you would be correct... but this isn't as easy as it sounds because the number of seeds we need to generate for each landscape differs between landscapes. The only way to work out how many seeds are required for a particular landscape is to go through the generation process, and that's a pretty hefty task.

If you wanted to generate a secret code yourself, then let's look at how you might calculate how many seeds to generate before you got to the four seeds for the secret code.

First, there's the landscape generation part, which is pretty easy:

  • Initialise the seeds (see step 1).
  • Generate and discard 81 seed numbers (see step 2).
  • Generate 1 seed number for the tile data multiplier (see step 3).
  • Generate 1024 seed numbers for populating the tile data (see step 4).

So we always need to generate 1106 seeds to create the landscape tiles. That's the easy part: now, let's look at adding enemies to the landscape:

  • Generate a seed for each attempt to calculate the number of enemies (see step 1).
  • Generate a seed for each attempt to choose a tile block at the current altitude for adding enemies (see step 3).
  • Once we have a suitable tile, we call SpawnObjectOnTile, which generates one seed to set the enemy's yaw angle (see step 3).

  • Generate one seed for the enemy's tactics timer and yaw step (see step 3).

The first two loops are unpredictable, so we have to run through them to know how many seeds to generate for this part. And the same uncertainty applies when we add the player, in step 5 of the process:

  • If this is landscape 0000, then we call SpawnObjectOnTile, which generates one seed to set the object's yaw angle.

  • Otherwise we call SpawnObjectBelow, which calls GetNextSeed0To30 twice for each attempt to find a tile, with up to 255 attempts at each altitude, working up by one altitude if we run out of attempts.
  • In the above step, GetNextSeed0To30 itself fetches a seed and clips it to the range 0 to 31, but if the result is 31 then it repeats the process until the result is in the range 0 to 30.

  • Once we have a suitable tile, we call SpawnObjectOnTile, which generates one seed to set the player's yaw angle.

Again, the loops in SpawnObjectBelow and GetNextSeed0To30 are unpredictable in size. The same happens when we add the trees in step 6 of the process, as not only do we have to generate an unpredictable number of seeds in order to add a tree, but the number of trees we need to add is governed by yet another pseudo-random seed:

  • Generate a seed to calculate the number of trees we should add.
  • For each tree we call SpawnObjectBelow, which calls GetNextSeed0To30 twice for each attempt to find a tile, with up to 255 attempts at each altitude, working up by one altitude if we run out of attempts.
  • As before, GetNextSeed0To30 itself fetches a seed and clips it to the range 0 to 31, but if the result is 31 then it repeats the process until the result is in the range 0 to 30.

  • Once we have a suitable tile, we call SpawnObjectOnTile, which generates one seed to set the tree's yaw angle.

That takes care of generating and populating the landscape, so we can finally move on to the secret code:

  • Generate and discard 38 seed numbers.
  • Generate the next four seed numbers in the sequence and use these to form the secret code.

Phew! That's quite an effort, and there are plenty of unpredictable steps. As a result, if we include the four seeds required for the secret code, we need to generate anywhere between 1211 seeds (for landscape 6774) and 1879 seeds (for landscape 9915). This isn't the sort of range for which you can just hazard a guess.

At this point I can heartily recommend Simon Owen's sentcode.py project, which generates the secret codes for every single landscape in The Sentinel, and not just on the BBC Micro, but on the Commodore 64, Amstrad CPC, Spectrum, PC and Amiga as well (as they have subtly different algorithms). It's an excellent resource, but how does it get around the complex question of how many seeds to generate for each level? Well, Simon has extracted that number from the game itself and captured it in a binary file, using the game itself to calculate the number of seeds needed for each landscape... by actually generating each landscape and counting how many seeds were used. Because, as we've seen above, that's the only way to do it.

To prove the point, I've copied the sentcode.py routines that convert the last four seeds into a code, and have added them to my fork of Simon's even more impressive sentland.py project, which I used to produce the landscape images in the deep dive on generating the landscape. In this version, we don't need the binary file to tell us how many seeds to generate, as instead we actually generate the landscape, and then generate the secret code, just as in the game. You can see the secret code in the extended data that's printed if you add the -x flag when running the script.

On top of this complexity, there's a whole slew of anti-cracker code that tries to detect whether the game is being hacked. If it is, then the game calls the CorruptSecretCode routine to corrupt the secret code; all it takes is an extra call to GetNextSeedNumber to fetch an extra seed number, and that's it - the secret code shown on-screen won't work.

See the deep dive on anti-cracker checks for details of this and all the other protections that Geoff Crammond baked into The Sentinel to stop people from generating secret codes themselves...