Passer au contenu principal

Comment concevoir des récompenses quotidiennes Roblox sans subventions dupliquées

Diagramme des systèmes Roblox LiveOps et leurs limites d'exécution protégées.

Un design pratique serveur-autorisant pour les récompenses quotidiennes Roblox qui résiste aux demandes répétées, serveurs simultanés, défaillances datastore et subventions partielles.

Traiter une réclamation comme une transaction, pas comme une touche

Une interface de récompense quotidienne semble simple : afficher un calendrier, mettre en évidence aujourd'hui, et laisser le joueur revendiquer. La partie difficile est tout derrière ce bouton. Un client peut envoyer la même demande à plusieurs reprises. Deux serveurs peuvent observer le même état stocké. Une requête datastore peut échouer après qu'une partie de la logique de récompense ait été exécutée. Un joueur peut se déconnecter entre la persistance et la présentation.

Le modèle plus sûr est une transaction détenue par le serveur. Le client demande à réclamer, mais il ne décide jamais si une récompense est due, ce que la récompense contient, ou si la subvention a réussi. Le serveur dérive la période de récompense actuelle, charge l'état de profil faisant autorité, valide la récompense configurée, tente une transition d'état atomique, et seulement ensuite renvoie un résultat que le client peut afficher.

Cette séparation est la règle centrale: l'interface utilisateur communique l'intention; le serveur possède l'éligibilité et la mutation.

Définir le plus petit état faisant autorité

Les récompenses quotidiennes n'ont pas besoin d'une histoire de chaque interaction d'interface. Ils ont besoin d'un état suffisamment durable pour répondre à trois questions : quelle période de récompense a été engagée pour la dernière fois, quelle position de stries suit de cet état, et si la requête actuelle peut faire avancer l'état.

Un profil pratique a généralement besoin d'une clé de période stable, d'une clé de dernière demande et d'une valeur de stries. La clé de période doit provenir du temps du serveur et d'une politique de réinitialisation documentée. Que le produit se réinitialise à minuit UTC, à une frontière régionale configurée ou après une durée de roulement est une décision de produit, mais la décision doit être déterministe sur le serveur.

Le calendrier configuré peut définir les identifiants de récompense, les quantités, les noms de devises, le comportement des stries et les dates actives. L'état du joueur doit se référer à une version de configuration validée ou à un identifiant de récompense stable plutôt que de copier des charges utiles arbitraires dans le stockage.

Valider la télécommande avant de toucher le stockage

Le guide de sécurité Roblox traite chaque valeur traversant la limite client-serveur comme non fiable. Pour une réclamation quotidienne, la requête doit être délibérément ennuyeux. Dans de nombreux modèles, le serveur n'a pas besoin de montant de récompense, de numéro de stries ou d'index de jour du client du tout; il peut dériver ces valeurs lui-même.

Avant le début des travaux de datastore, le serveur devrait appliquer une petite chaîne de rejet:

  • confirmer la requête arrivée sur le serveur prévu RemoteEvent ou RemoteFunction;
  • rejeter les types inattendus, les identifiants supplémentaires ou les charges utiles malformées;
  • les tentatives de revendication de limite de taux par lecteur sur le serveur;
  • vérifier que le profil du lecteur et la configuration des fonctionnalités sont disponibles;
  • refuser les demandes alors qu'une réclamation pour ce joueur est déjà traitée localement.

La limitation des tarifs n'est pas la garantie dupliquée. Elle protège la capacité du serveur et réduit les abus. La garantie durable doit encore survivre à deux serveurs, les retries, reconnects et redémarrages de processus.

Faire de l'éligibilité et de l'engagement une mise à jour atomique

Un `GetAsync()` suivi plus tard par `SetAsync()` laisse une fenêtre de course. Deux serveurs peuvent lire le même état non réclamé, les deux décident que le lecteur est éligible, et les deux écrivent une subvention. Roblox documents `UpdateAsync()` comme l'opération multi-serveurs plus sûre parce qu'il lit la valeur courante et réinitialise le callback en cas de conflit de mises à jour simultanées.

Placez la décision d'admissibilité à l'intérieur de cette mise à jour atomique. Le callback reçoit la dernière valeur stockée, vérifie si la période actuelle a déjà été engagée, et retourne soit un nouvel état ou `nil` pour annuler. Le callback ne doit pas céder, de sorte qu'il ne doit pas appeler des services externes, attendre des instances, ou effectuer la récompense visible lui-même.

L'État engagé devrait inclure un marqueur de subvention durable: la période de récompense et l'identificateur de récompense stable ou la clé de transaction qui a remporté la mise à jour. Si le callback découvre que le marqueur existe déjà, le résultat correct est déjà réclamé, et non une autre subvention.

Ce modèle convertit les demandes répétées en lectures répétées du même résultat. La première transition valide gagne. Les tentatives ultérieures observent le marqueur engagé et deviennent des non-ops.

Combler l'écart entre la persistance et la récompense

L'état du profil atomique empêche deux transitions de réclamation, mais la conception nécessite toujours un ordre de livraison clair. Si la monnaie est ajoutée avant que le marqueur de réclamation soit durable, une défaillance datastore peut laisser la valeur accordée sans enregistrement. Si le marqueur est stocké en premier et le serveur s'écrase avant la livraison, le lecteur peut avoir une réclamation engagée sans l'élément.

La réponse solide est un dossier de subvention idémpotent. Commettez un identifiant de transaction et un état de livraison, puis assurez-vous que la livraison de récompense se répète contre cet identifiant. Les services de monnaie, d'inventaire, de badge ou de versement doivent reconnaître qu'une transaction particulière a déjà été appliquée.

Chaque expérience n'a pas besoin d'un grand livre complexe, mais chaque expérience a besoin d'une réponse explicite à la question de l'échec partiel.

Préserver l'ordre d'exploitation pendant les périodes de récupération

Les appels Datastore peuvent échouer, et la fondation d'exécution LiveOps Goblin utilise un wrapper de datastore sûr avec un comportement de réessayer et de backoff. La réessayer n'est pas la même que l'envoi de la même opération à partir de plusieurs tâches indépendantes. Roblox avertit que les rétries naïves peuvent réorganiser des opérations stateful.

Utilisez un chemin de réclamation sérialisé par lecteur, conservez l'ordre des changements de profil, et gardez l'identificateur de transaction stable à travers les relevés. Réessayez seulement l'opération échouée avec un backoff limité. Ne recalculez pas une nouvelle période de récompense ou clé de transaction sur chaque tentative. Si le joueur quitte, la récupération durable devrait être en mesure de continuer à partir de l'état stocké plutôt que d'un drapeau en mémoire.

Les diagnostics devraient distinguer les demandes rejetées, les limites de taux, les avertissements de datastore, les résultats déjà réclamés, les réclamations engagées et la récupération de la livraison.

Gardez la configuration en sécurité avant qu'elle n'atteigne le jeu

La protection du duplicata est affaiblie lorsque le calendrier de récompense exporté lui-même est ambigu. LiveOps Goblin valide des quantités impossibles, des clés de datastore manquantes, des noms de devises ambigus, des codes duplicata et des horaires expirés avant de produire une configuration Roblox en version.

Une définition de récompense devrait utiliser des identifiants stables, des quantités limitées, des cibles de devises ou d'inventaire connues, des règles de calendrier explicites et une version qui peut être tracée dans les diagnostics. Les calendriers vides devraient rester vides jusqu'à ce qu'un créateur ajoute de vraies récompenses.

Lorsque le calendrier change, décidez du traitement des joueurs au milieu d’une série. Des identifiants de récompense stables et une configuration versionnée permettent de tester cette politique. Si seule la position compte, le « troisième jour » peut désigner des récompenses différentes avant et après une mise à jour.

Testez les échecs, pas seulement le chemin heureux

Une matrice d'essai de récompense quotidienne utile devrait inclure plus de une fois et recevoir des pièces.

  • deux demandes rapides du même client;
  • les requêtes provenant de deux contextes de serveur portant sur le même profil stocké;
  • une réclamation pour une période déjà engagée;
  • charge utile à distance mal formée et comportement de limite de taux;
  • conflit de datastore, défaillance transitoire et chemins de réessayer épuisés;
  • déconnecter après l'engagement mais avant la confirmation du client;
  • réessayer la livraison en utilisant le même identifiant de transaction;
  • un changement de version du calendrier pendant une série active;
  • une configuration de récompense manquante ou non valide;
  • l'analyse est désactivée avec des diagnostics qui fonctionnent toujours.

Le résultat attendu n'est pas seulement un crash. Chaque test devrait affirmer l'état définitif durable, le nombre de subventions appliquées, le résultat visible du client et l'événement diagnostique.

Une liste de contrôle concise

La conception finale peut être revue avec dix questions. L'admissibilité est-elle dérivée sur le serveur? Le temps de réinitialisation est-il déterministe? Le client n'envoie-t-il que l'intention? Les requêtes à distance sont-elles validées et le taux du serveur est-il limité? La transition de revendication est-elle atomique? L'état stocké comprend-il un marqueur de transaction stable? La livraison de récompense peut-elle être répétée sans duplication?

Lorsque les dix réponses sont concrètes, une récompense quotidienne cesse d'être un gestionnaire de boutons fragile et devient une petite transaction LiveOps compréhensible.

Système connexeExplorer LiveOps Goblin