1. Tyamka
  2. Devlog
  3. Checking scores by replaying them on the server

Checking scores by replaying them on the server

· 3 min read · Tyamka team

How the Tyamka leaderboards check a result by rebuilding the level and replaying the player's moves with the same code, and what this still cannot catch.

A leaderboard is only worth something if the scores on it are real. The simple version trusts whatever number the game sends, and that number is easy to fake. For the web and Telegram leaderboards we went another way. The server does not believe the score. It rebuilds the level, plays the player's moves back through the same game code and keeps the score it got itself. This part is written and tested. It is not live yet, because the server it runs on has not been set up.

Why the same code runs on the server

It goes back to a rule from the first day of code. The rules of each game live in a plain TypeScript module with no React inside. Randomness comes in as a seed and time comes in as an argument. So a level is fully described by the game, the level number and the seed, and a run is that plus the list of moves with their times.

All fifteen games are described in the same shape, and the game shell changes the state of a game in one place only. That is why a single replay function covers every game. We had planned a separate piece of work for it and did not need one. The function is about 40 lines, and the game modules run on the server under Deno without changes. Rebuilding the hardest level of any game took at most 7.2 ms in our test on 26 September, so replay adds little to a request.

What the game sends

During a run the shell records each move that actually changed the game, with the time it happened. Moves that change nothing are left out. At the end, if the player has an account, the run goes to the server with the game, the level and the seed. The server replays it and compares. If its score differs from the one the game sent, nothing is written. The word grid is replayed with the dictionary of the language it was played in, so English and Ukrainian words never mix on one board.

Guests play as before. Their results stay on the device and nothing is sent. Saving on the device does not depend on the network at all. Without a connection the run waits in a queue on the device and goes out when the app comes back, and the results screen says it will be sent later instead of pretending it already was.

What it cannot catch

Replay proves that the score comes from a legal run on that level. It does not prove that a person played it at that speed. The times of the moves come from the device, so a script that sends perfect moves will pass. We wrote that limit into the code next to the check rather than leaving it unsaid. The next step is for the server to hand out the seed for every run and to set a floor on believable reaction times.

How it is tested

The replay has 27 tests of its own. For each of the fifteen games, and for the word grid in both languages, a bot plays level 1 and the last level to the end. The replay of the moves it recorded must then give the same score and the same outcome. Other tests feed it bad runs, such as an unfinished game, a move after the end, time running backwards or too many moves, and every one of them has to be rejected.

Where it stands

The server side and the recording of moves in the game are written. The live server and the leaderboard screen are still missing, and the screen design is waiting for approval. Until then the check lives in tests. The same idea is behind the rule for the Schulte table and the other games that a best score on the device is never made up. If a result failed to save, the game says so.