Currently Leaderboards can be edited by developers on the website. It's a pretty hefty interface and a difficult job to get a leaderboard working just right. It genuinely takes some care and attention to get a good leaderboard working, so in this doc we'll try to understand how it works.
This is how a game's Leaderboard List looks like on the website:
In the center you can see every already made Leaderboard, and in the right column you can see the Code Notes for the game. The Code Notes are here to help with some conditions we'll see below.
Here's a brief explanation of each field of a single Leaderboard:
- Title: the leaderboard's title.
- Description: the leaderboard's description.
- Format: it can be
Value. It's used to distinguish what sort of leaderboard this is (we'll go into this below).
- "LowerIsBetter" checkbox: when checked, it means that a lower value is a better thing. It's usually the case for time based leaderboards, whereas a larger score generally is better.
- Start: start conditions, aka STA.
- Cancel: cancel conditions, aka CAN.
- Submit: submit conditions, aka SUB.
- Value: a value interpreter, aka VAL.
Note: A valid leaderboard MUST have all four of these conditions.
Those last 4 fields are really important and LOTS of care must be taken over the entry of any characters into these strings. That's why they deserve a further explanation:
The STA or Start condition is a series of values, like an achievement, that must be true in order to start looking for a leaderboard submission. Once the STA is true, the game will activate the other three, and will keep processing them constantly.
If a Cancel (CAN) condition is true, then it will cancel all progress towards the leaderboard submission. If however the Submit (SUB) condition is true, then the leaderboard entry will be submitted.
Finally, the Value (VAL) is a special case, and will be taken from memory using the formula stated in the memory box.
The memory addresses for STA/CAN/SUB/VAL have the following format:
|location/size||prefix (the letters can be in lower case)||example|
The best place to start is to look at one of the existing leaderboards http://retroachievements.org/leaderboardList.php and break it down to see how it works. We're going to use the Green Hill Act 1 (Sonic the Hedgehog) Leaderboard for this purpose. Then let's see how it looks:
The Title/Description fields are quite obvious.
The Type is "Time (Frames)". In Sonic every 60 frames is 1 second, then we'll monitor the time using the frames.
The Lower Is Better flag is checked, then the one who makes the shortest time will be the #1.
Now we're going to break down the most important parts.
0xfe10=h0000: If RAM address 0xfe10 is equivalent to hex 0000,
0xhf601=h0c: If 8-bit RAM address 0xf601 is equivalent to hex 0c,
d0xhf601!=h0c: If the previous 8-bit RAM address 0xf601 is NOT equivalent to hex 0c,
0xfff0=0If RAM address 0xfff0 is equivalent to 0.
This might seem daunting, because we don't know what these addresses mean. That's why the Code Notes in the right column are pretty handy! You can see how these addresses are labelled in memory. In our example we have:
- 0xfe10 is the level, and is expected to be 0 (the first level).
- 0xf601 is an 8-bit memory address, and we use the prefix '0xh' instead of '0x' to signify this. 0xf601 is the screen mode. The second and third parts of this start statement are saying 'the current mode should be 'ingame' (0c), and the previous mode should NOT be 'ingame'. Note that 'd' represents delta, or "the previous frame's value". In other words, trigger this if we've JUST arrived in a level (the start of the level, when we want to start testing their time).
- Finally we also expect 0xfff0 to be equivalent to 0, because 0xfff0 is the demo mode, and we don't want to award a leaderboard entry when the demo is active!
- 0xfe13 is the number of lives.
The cancel section checks if the player's LIVES counter ever becomes lower. Literally, it says "Cancel if the CURRENT value at 0xfe13 is less than the PREVIOUS value at 0xfe13". We want to do this because you could reach the final checkpoint and run out of time, resetting your timer to 0:00. We don't want to allow this, because it's not the correct way of completing the level. So if the player dies, we reset their leaderboard progress.
- 0xf7cc is the endlevel flag, non-interactive.
The submit section checks if the current frame has the 'endlevel' flag set to true (or
!=0, 'nonzero'), and the previous frame (delta) has it set to false (or
=0, 'zero'). This suggests that the player has reached the end of the level, and has proven to be a fairly sturdy benchmark.
Tip: it can be useful to watch these values in memory to see how they perform, and what sort of values they end up at in different circumstances.
Finally, value. Once the player has reached the start condition, they will be shown a popup which remains on-screen, showing their progress so far. If it's a time leaderboard, it will be a clock, and if it's a score, it will just be the value. If they fulfill the cancel condition, they will be told that they have failed, and the popup will be removed. If they successfully reach the submit condition, the current value will be taken and submitted as their score, and on successful submission, an ingame popup will inform the player of the leaderboard so far, and their position in the leaderboard.
The value condition is special in a few ways. It is evaluated constantly and shown on-screen all the time when the leaderboard is active. It doesn't work like the other conditions, it expects addresses in the following way:
address*modifier (address times modifier)
and it uses the
_ underscore operator as a 'plus'. The
* asterisk signifies 'multiply', so in the value
8-bit 0xfe24 times 1, PLUS 8-bit 0xfe25 times 60, PLUS 8-bit 0xfe22 times 3600
The reason for this is that the values in each of these addresses signifies frames, seconds, and minutes respectively. When we add these values together, we get a grand total in frames that we submit to the database.
Remember that 'Format' field that can be either Score, Time (Frames), Time (Milliseconds) or Value? Time (Frames) is the most common one, and represents 'frames'. Time (Milliseconds) expects a value that we can convert directly into millisecs (Super Mario Kart uses this). However to convert a value in frames into a human-readible format, we should divide the value by 60 to get an accurate representation of seconds, and a value in millisecs should be divided by 100 to get the number of seconds. This is used both on the website and in the app to display the value properly, and is important to distinguish so we can be sure we get the most accurate value out of the emulator, by using whatever format they use to record time.
There are unfortunately MANY ways to get this process wrong, so if you are having any trouble feel free to ask for help in our Discord server.
If you want to practice, it's highly recommended to create your own leaderboard and attempting something on a new game, rather than using an existing leaderboard.
Please remember that these files are pulled directly into someone's game if they decide to play it, and a badly formed memory address or string could cause their emulator to crash, so please test your leaderboard code!