ELK GAMEVERSE
Commencer

Pourquoi nous rejouons chaque partie sur nos serveurs

Un classement ne vaut la peine que si personne ne peut y écrire directement. Voici ce que cela coûte et ce que cela rapporte.

La manière évidente de construire un jeu est de laisser le téléphone décider. Le joueur termine, l'application calcule le score, et elle raconte au serveur ce qui s'est passé. C'est rapide, c'est simple, et le serveur n'a aucun avis.

Cela signifie aussi que le score est ce que l'application en dit. Or une application est un programme sur l'ordinateur de quelqu'un d'autre, qu'il peut modifier.

Ce que nous faisons à la place

Chaque jeu enregistre les coups joués — pas le résultat, les coups. À la fin, cette liste part vers nos serveurs, et le serveur rejoue la même partie depuis la même position de départ, en appliquant les mêmes règles aux mêmes coups.

S'il aboutit à votre résultat, la partie est acceptée et la récompense est calculée depuis la copie du serveur. Sinon, la partie est refusée et rien n'est crédité.

Le moteur est le même code des deux côtés, et il est déterministe : à graine et coups identiques, il doit produire le même plateau. Cette exigence façonne une part surprenante de la conception. Tout ce qui est aléatoire doit venir d'une graine que le serveur détient aussi. Tout ce qui lit l'horloge doit lire un temps sur lequel les deux côtés s'accordent. Une règle qui se comporte autrement sur un appareil rapide que sur un lent est une règle que nous ne pouvons pas avoir.

Ce que cela coûte

Quelques millisecondes par partie terminée, et une vraie contrainte sur la façon d'écrire les jeux. Cela trouve aussi nos propres bogues, ce qui est moins confortable qu'il n'y paraît.

Un exemple, parce que c'est le genre honnête. Une récolte était distribuée en parcourant les clés d'un objet JSON. Deux copies du même contenu — l'une chargée depuis un fichier, l'autre depuis la base — renvoyaient ces clés dans un ordre différent, et l'entrepôt se remplissait en cours de route. Mêmes règles, mêmes coups, résultat différent. Des joueurs honnêtes voyaient leur partie refusée avec un message parlant d'un établissement détenant 590 charges au lieu de 591. Rien ne semblait anormal dans le jeu ; le rejeu était simplement en désaccord avec lui-même.

Ce bogue était invisible pour tous les tests unitaires, car chaque moitié passait de son côté. Il a fallu que les deux côtés soient alimentés par des routes différentes pour l'exposer.

Ce que cela rapporte

Un classement dans lequel personne ne peut écrire directement, et une récompense qu'on ne peut pas inventer. C'est la même propriété énoncée deux fois : ce que vous gagnez est ce que le rejeu dit que vous avez gagné.

Cela nous permet aussi d'être précis quand quelque chose échoue. Une partie refusée n'est pas une accusation vague : c'est un écart en un point connu, et presque toujours une connexion coupée en cours de partie plutôt qu'un acte du joueur.