Module 02 · Leçon 4 sur 10
L’art du prompt précis
La structure d’un prompt qui donne du bon code Roblox, rubrique par rubrique, et huit exemples « mauvais prompt → bon prompt » sur de vrais systèmes de jeu, avec le résultat expliqué.
- ⏱️ 1 h
- 📝 Quiz de 6 questions
- 🔄 Vérifié le 24 octobre 2026
Sur cette page
- Pourquoi la précision change tout
- Le gabarit en huit rubriques
- Ce que change chaque rubrique
- Exemple 1 : la sauvegarde des joueurs (DataStore)
- Exemple 2 : la boutique
- Exemple 3 : le combat (une épée)
- Exemple 4 : l’inventaire
- Exemple 5 : l’interface (UI)
- Exemple 6 : le système de manches (round system)
- Exemple 7 : l’anti-triche
- Exemple 8 : le classement mondial (leaderboard)
- Les erreurs qui sabotent un bon prompt
- Même un excellent prompt ne dispense pas de relire
- Quiz de fin de leçon
🎯 Dans cette leçon, tu vas apprendre à…
- Structurer un prompt en huit rubriques et comprendre ce que chacune change dans le code produit
- Transformer un prompt vague en prompt précis pour un système Roblox réel
- Prévoir ce qu’une IA produit avec un prompt vague pour repérer ces défauts dans son code
À la leçon précédente, tu as installé Claude Code et écrit le CLAUDE.md de l’Obby des Nuages. Il reste le geste que tu feras des centaines de fois : écrire un prompt, c’est-à-dire la demande que tu envoies à l’IA. C’est lui qui décide, plus que tout le reste, de la qualité du code obtenu. Un prompt vague donne du code vague. Dans cette leçon, tu apprends une structure en huit rubriques, puis tu la vois à l’œuvre sur huit systèmes de jeu réels.
Pourquoi la précision change tout
Une IA de code ne devine pas ce que tu as en tête. Quand une information manque, elle la remplace par la solution la plus courante qu’elle connaît : des noms en anglais, un script unique, un système « qui marche » dans le cas simple. Elle produit rarement du code absurde ; elle produit du code générique, qui ne s’emboîte pas dans ton jeu et oublie ce que tu n’as pas demandé.
La documentation d’Anthropic le formule ainsi :
- « Claude can infer intent, but it can’t read your mind. Reference specific files, mention constraints, and point to example patterns. » (Claude peut déduire une intention, mais il ne lit pas dans les pensées : cite des fichiers précis, mentionne les contraintes, montre des exemples à suivre.)
- « The more precise your instructions, the fewer corrections you’ll need. » (Plus tes instructions sont précises, moins tu auras de corrections à faire.)
- « Think of Claude as a brilliant but new employee who lacks context on your norms and workflows. » (Vois Claude comme un employé brillant mais nouveau, qui ne connaît ni tes règles ni tes habitudes.)
Et un test très simple, tiré du même guide : « Show your prompt to a colleague with minimal context on the task and ask them to follow it. If they’d be confused, Claude will be too. » Si un ami développeur qui n’a jamais vu ton jeu ne saurait pas quoi faire avec ton prompt, Claude non plus.
Le gabarit en huit rubriques
Tous les prompts complets de cette formation suivent la même structure. Copie-la et garde-la sous la main :
Contexte : (le jeu, la partie du jeu concernée, ce que fait déjà le projet)
Ce qui existe déjà : (scripts, modules, remotes, instances à réutiliser — avec leurs chemins)
Objectif : (le résultat exact attendu, du point de vue du joueur)
Fichiers concernés : (fichiers à créer / modifier ; « ne modifie rien d'autre »)
Contraintes techniques : (type de script et emplacement, API à utiliser ou à éviter, style)
Sécurité : (ce que le serveur doit valider, ce qui ne doit jamais venir du client)
Cas limites : (joueur qui part, valeurs invalides, double clic, panne du DataStore…)
Vérification : (comment prouver que ça marche : test dans Studio, messages attendus dans l'Output, selene/StyLua sans erreur)Pour t’en servir, remplace le texte entre parenthèses de chaque rubrique par les informations de ta tâche (et supprime les parenthèses). Tu peux omettre une rubrique sans objet (pas de « Sécurité » pour un effet purement visuel), mais jamais « Objectif » ni « Vérification ». Pour un gros changement, commence le prompt par « Commence par me proposer un plan (mode plan), sans modifier de fichier. », ou active le mode plan avec Shift+Tab (leçon 3).
Ce que change chaque rubrique
| Rubrique | Sans elle, l’IA… | Avec elle… |
|---|---|---|
| Contexte | imagine un jeu générique (souvent un « simulator » ou un jeu de combat) | adapte le code à un obby, à ton public mobile, à ton projet Rojo |
| Ce qui existe déjà | recrée ce qui existe : un deuxième leaderstats, un autre DataStore, un RemoteEvent au nom différent | réutilise tes modules, tes remotes et tes noms : le nouveau code s’emboîte dans l’ancien |
| Objectif | choisit elle-même où s’arrêter : trop peu, ou dix fonctionnalités non demandées | vise un résultat observable par le joueur, donc vérifiable |
| Fichiers concernés | range le code où elle veut et « améliore » au passage des fichiers que tu n’as pas demandés | crée exactement les bons fichiers, avec le bon suffixe (.server.luau, .client.luau, .luau), et ne touche à rien d’autre |
| Contraintes techniques | utilise l’API la plus connue, même dépassée (wait, SetAsync partout) |
utilise les API que tu as choisies, dans ton style |
| Sécurité | fait « marcher » le système, souvent en croyant ce que le client envoie | met la décision sur le serveur et valide chaque argument |
| Cas limites | code le cas heureux : un joueur, tout se passe bien | gère le joueur qui part, le double clic, la panne du DataStore |
| Vérification | s’arrête quand le code « a l’air fini » | lance selene et StyLua, te dit comment tester et quoi voir dans l’Output |
Deux principes d’Anthropic expliquent les deux dernières lignes. Pour la sécurité et les cas limites : « If you want Claude to do more than the task strictly requires, explicitly request it » (si tu veux plus que le strict nécessaire, demande-le explicitement). Pour la vérification : « Give Claude a check it can run » (donne à Claude un contrôle qu’il peut lancer), et « Have Claude show evidence rather than asserting success » (demande-lui des preuves plutôt qu’une affirmation).
Passons à la pratique. Pour chaque exemple : le mauvais prompt, ce qu’une IA produit typiquement avec lui, le bon prompt, et ce qu’il change. Les défauts décrits ne sont pas inventés pour faire peur : ce sont des choix par défaut fréquents d’une IA à qui on n’a rien précisé (une IA peut aussi faire mieux, ou autrement). Tu apprendras à les repérer dans les modules 3 à 7.
Exemple 1 : la sauvegarde des joueurs (DataStore)
Le mauvais prompt :
fais une sauvegarde pour mon jeu
Ce que l’IA produit typiquement. Un seul Script, avec un DataStore au nom inventé, une clé égale au seul UserId, et ses propres leaderstats, comme ceci :
-- Script produit par « fais une sauvegarde pour mon jeu » (à NE PAS utiliser)
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local store = DataStoreService:GetDataStore("PlayerData")
Players.PlayerAdded:Connect(function(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Parent = leaderstats
-- Aucun pcall : à la moindre panne du DataStore, erreur et script arrêté pour ce joueur
local data = store:GetAsync(tostring(player.UserId))
if data then
coins.Value = data
end
end)
Players.PlayerRemoving:Connect(function(player)
local leaderstats = player:FindFirstChild("leaderstats")
local coins = leaderstats and leaderstats:FindFirstChild("Coins")
if coins and coins:IsA("IntValue") then
-- Si le chargement avait échoué, on écrase la vraie sauvegarde avec 0
store:SetAsync(tostring(player.UserId), coins.Value)
end
end)
Les défauts : un dossier leaderstats en double de celui de ton jeu (avec Coins au lieu de Pieces) ; l’étape n’est pas sauvegardée ; aucun pcall ni nouvelle tentative ; pas de BindToClose, donc la progression des joueurs présents peut être perdue quand le serveur ferme ; et le pire : si le chargement échoue, le joueur démarre à 0 et sa vraie sauvegarde est remplacée par 0 quand il part.
Le bon prompt :
Commence par me proposer un plan (mode plan), sans modifier de fichier.
Contexte : Obby des Nuages, projet Rojo (voir CLAUDE.md). Aujourd'hui, un joueur qui quitte perd sa progression.
Ce qui existe déjà : src/ServerScriptService/Leaderstats.server.luau crée à l'arrivée du joueur le Folder leaderstats avec IntValue Etape et IntValue Pieces. Aucune donnée n'est sauvegardée.
Objectif : un joueur qui quitte puis revient retrouve son étape et ses pièces, même si le serveur a fermé entre-temps.
Fichiers concernés : crée src/ServerScriptService/DonneesJoueurs.luau (ModuleScript avec Charger, Obtenir et Sauvegarder) et src/ServerScriptService/GestionJoueurs.server.luau, qui remplace Leaderstats.server.luau (supprime-le) et crée les leaderstats à partir des données chargées. Ne modifie rien d'autre.
Contraintes techniques : DataStore "DonneesJoueurs_v1", clé "Joueur_" .. player.UserId ; une table { version = 1, etape = 0, pieces = 0, inventaire = {}, equipe = "" } (inventaire et equipe serviront à la boutique) ; UpdateAsync pour écrire ; chaque appel dans un pcall, 3 tentatives avec une attente croissante (task.wait) ; sauvegarde à PlayerRemoving, toutes les 180 secondes, et dans game:BindToClose.
Sécurité : seul le serveur lit et écrit ces données ; aucun RemoteEvent ne permet au client de modifier ses pièces ou son étape.
Cas limites : si le chargement échoue après 3 tentatives, préviens le joueur et ne remplace JAMAIS sa sauvegarde par des valeurs par défaut ; joueur qui part avant la fin du chargement ; serveur qui ferme avec plusieurs joueurs.
Vérification : selene et StyLua sans erreur ; explique-moi le test en Play sur une copie de test avec « Enable Studio Access to API Services » activé (gagner des pièces, quitter, relancer, retrouver les mêmes valeurs) et les messages attendus dans l'Output.Ce que ça change : Claude garde tes leaderstats Etape et Pieces (mêmes noms, chargées depuis la sauvegarde) au lieu d’en inventer d’autres, sauvegarde les deux valeurs sous le nom et la clé de la formation, protège chaque appel réseau et gère la fermeture du serveur. Surtout, le cas limite « ne jamais écraser une sauvegarde après un chargement raté » est écrit noir sur blanc : c’est la différence entre un bug gênant et des joueurs qui perdent des heures de progression. Le détail de ce système est au module 5.
Exemple 2 : la boutique
Le mauvais prompt :
fais une boutique pour acheter des trails
Ce que l’IA produit typiquement. Une interface construite par un LocalScript, qui vérifie elle-même si le joueur a assez de pièces, puis envoie au serveur le nom de l’objet et son prix. Côté serveur :
-- Script serveur produit par « fais une boutique » (faille : à NE PAS utiliser)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Remote créé par script, avec un nom anglais, au lieu de Remotes.AcheterObjet
local buyEvent = Instance.new("RemoteEvent")
buyEvent.Name = "BuyItem"
buyEvent.Parent = ReplicatedStorage
buyEvent.OnServerEvent:Connect(function(player, itemName, price)
local coins = player.leaderstats.Coins
if coins.Value >= price then
-- Le prix vient du client : un tricheur envoie -1000 et GAGNE des pièces
coins.Value -= price
local owned = Instance.new("BoolValue")
owned.Name = itemName
owned.Parent = player
end
end)
Les défauts : le serveur croit le prix envoyé par le client (prix négatif, nul, ou objet qui n’existe pas : tout passe) ; aucune vérification du type des arguments ; les prix sont recopiés en dur dans le LocalScript au lieu de venir du Catalogue ; l’achat n’est pas sauvegardé ; on peut racheter dix fois le même objet ; un clic frénétique envoie cent demandes.
Le bon prompt :
Commence par me proposer un plan (mode plan), sans modifier de fichier.
Contexte : Obby des Nuages, projet Rojo (voir CLAUDE.md). Les joueurs doivent pouvoir acheter des traînées avec leurs pièces.
Ce qui existe déjà : ReplicatedStorage.Modules.Catalogue (identifiant → nom, prix, couleurs) ; le RemoteEvent ReplicatedStorage.Remotes.AcheterObjet ; les pièces du joueur dans leaderstats.Pieces ; le ModuleScript serveur DonneesJoueurs (Obtenir(player) renvoie sa session, avec le champ inventaire ; les leaderstats y sont recopiées à chaque sauvegarde) ; le module serveur Validation (estTexte, creerCooldown).
Objectif : le joueur clique sur « Acheter » ; s'il a assez de pièces, elles sont débitées, la traînée apparaît dans son inventaire et reste achetée après reconnexion. Sinon, un message lui dit combien il lui manque.
Fichiers concernés : crée src/ServerScriptService/Boutique.server.luau ; ne modifie ni Catalogue, ni DonneesJoueurs, ni l'interface.
Contraintes techniques : Script serveur ; le client envoie UNIQUEMENT l'identifiant de l'objet ; réponse au joueur par le RemoteEvent Remotes.Notification.
Sécurité : le prix est TOUJOURS lu dans Catalogue côté serveur ; refuser un identifiant qui n'est pas un texte ou qui n'existe pas dans Catalogue ; un achat par seconde maximum par joueur ; refuser un objet déjà possédé.
Cas limites : pièces insuffisantes, identifiant inconnu ou trop long, double clic, joueur dont les données ne sont pas encore chargées, joueur qui part pendant l'achat.
Vérification : selene et StyLua sans erreur ; donne-moi un test en Play (achat réussi, achat refusé faute de pièces, deuxième achat du même objet refusé) et une ligne à taper dans la Command Bar de Studio (vue client) pour simuler un client qui envoie un identifiant inventé.Ce que ça change : le client ne fait plus que demander ; le serveur décide de tout, avec le prix du Catalogue. Les vérifications de type, d’existence, de possession et de délai sont demandées une par une, donc présentes dans le code et faciles à contrôler. La vérification inclut un test de triche, pas seulement le cas qui marche. Ces règles sont détaillées au module 4.
Exemple 3 : le combat (une épée)
L’Obby des Nuages n’a pas de combat, mais c’est l’un des premiers systèmes que les créateurs demandent pour un autre jeu. Il montre bien le piège du « ça marche chez moi ».
Le mauvais prompt :
fais une épée qui fait des dégâts
Ce que l’IA produit typiquement. Un Tool avec un Script qui inflige des dégâts à chaque contact de la lame (Touched) : l’épée blesse même quand on ne frappe pas, et un seul coup touche plusieurs fois. Ou, version « client », un LocalScript détecte la cible et envoie au serveur « inflige 50 dégâts à tel joueur » : le serveur obéit, et un tricheur tue n’importe qui à travers la carte. Souvent aussi une animation chargée avec Humanoid:LoadAnimation, une méthode dépréciée, et pas de délai entre deux coups.
Le bon prompt :
Contexte : jeu de combat Roblox, projet Rojo (voir CLAUDE.md). Je veux une épée simple pour une arène.
Ce qui existe déjà : le Tool « Epee » dans src/StarterPack (dossier déclaré dans default.project.json ; Handle + modèle), le RemoteEvent ReplicatedStorage.Remotes.Attaquer, ReplicatedStorage.Modules.Config.
Objectif : un clic déclenche un coup (animation) ; les adversaires placés devant le joueur, à moins de 6 studs, perdent Config.DEGATS_EPEE points de vie, une seule fois par coup.
Fichiers concernés : crée src/StarterPack/Epee/ClientEpee.client.luau et src/ServerScriptService/Combat.server.luau ; ajoute DEGATS_EPEE = 20 et DELAI_COUP = 0.6 à Config. Ne modifie rien d'autre.
Contraintes techniques : animation jouée côté client avec Animator:LoadAnimation ; détection des cibles côté serveur avec workspace:GetPartBoundsInBox devant le personnage ; dégâts avec Humanoid:TakeDamage.
Sécurité : le client envoie seulement « je frappe », jamais de cible ni de dégâts ; le serveur vérifie que l'attaquant est vivant et tient l'épée, applique DELAI_COUP entre deux coups, ne touche jamais l'attaquant lui-même ni un même adversaire deux fois par coup.
Cas limites : attaquant ou cible morts, cible sans Humanoid, joueur qui part pendant le délai, spam de clics.
Vérification : selene et StyLua sans erreur ; test en serveur local à 2 joueurs : un coup = 20 dégâts, spam = au plus un coup toutes les 0,6 s, aucun dégât à plus de 6 studs.Ce que ça change : c’est le serveur qui détecte les cibles et calcule les dégâts ; le client ne fait qu’annoncer une attaque. Le délai, la portée et le « une fois par coup » sont des chiffres précis, donc testables. L’API dépréciée est écartée d’office par la contrainte technique.
Exemple 4 : l’inventaire
Le mauvais prompt :
ajoute un inventaire
Ce que l’IA produit typiquement. Un dossier Inventory rempli de BoolValue sous le joueur, parfois créé par un LocalScript (donc invisible pour le serveur), sans lien avec ta sauvegarde : l’inventaire disparaît à la reconnexion. Pour équiper, un RemoteEvent reçoit le nom de l’objet et l’équipe sans vérifier que le joueur le possède. Et souvent une interface complète que tu n’avais pas demandée.
Le bon prompt :
Commence par me proposer un plan (mode plan), sans modifier de fichier.
Contexte : Obby des Nuages, projet Rojo (voir CLAUDE.md). Les traînées achetées doivent être conservées et équipables.
Ce qui existe déjà : DonneesJoueurs.luau (Obtenir(player) renvoie la session sauvegardée, qui contient déjà inventaire = {} et equipe = "") ; Boutique.server.luau qui gère l'achat, mais range les objets dans une table en mémoire, perdue à la reconnexion ; le module serveur Validation (estTexte, creerCooldown) ; ReplicatedStorage.Modules.Catalogue ; le RemoteEvent Remotes.EquiperObjet.
Objectif : chaque joueur possède une liste de traînées achetées et peut en équiper une (ou aucune) ; après reconnexion, il retrouve ses traînées et celle qu'il portait.
Fichiers concernés : remplace le contenu de Boutique.server.luau (achat rangé dans donnees.inventaire, gestion de EquiperObjet). Ne modifie ni DonneesJoueurs.luau ni l'interface.
Contraintes techniques : inventaire = table { [idObjet] = true } ; equipe = idObjet ou "" ; publie l'état pour l'interface avec des attributs du joueur (Possede_<idObjet>, TrailEquipe), que les LocalScripts lisent sans rien modifier.
Sécurité : EquiperObjet n'équipe qu'un objet du Catalogue que le joueur POSSÈDE dans donnees.inventaire (jamais d'après un attribut) ; "" retire la traînée ; types vérifiés côté serveur ; une demande par seconde maximum.
Cas limites : données du joueur pas encore chargées ; objet retiré du Catalogue mais présent dans un inventaire ; équiper l'objet déjà équipé ; joueur qui part.
Vérification : selene et StyLua sans erreur ; test en Play sur une copie de test avec l'accès aux API activé : acheter, équiper, quitter, revenir, la traînée est toujours là ; tenter d'équiper un objet non possédé depuis la Command Bar (vue client) ne fait rien.Ce que ça change : l’inventaire vit dans les données sauvegardées de DonneesJoueurs, que Claude ne doit pas réécrire, au lieu d’une table perdue à la reconnexion. La règle « on n’équipe que ce qu’on possède » est explicite : c’est exactement la faille du trouve-le-bug de ce quiz. Le périmètre est borné : pas d’interface surprise.
Exemple 5 : l’interface (UI)
Le mauvais prompt :
fais une belle interface pour la boutique
Ce que l’IA produit typiquement. Toute l’interface créée par code dans un LocalScript, avec des tailles en pixels (UDim2.fromOffset(400, 300)) : parfaite sur ton écran, coupée ou minuscule sur un téléphone. Une interface qui se ferme à chaque mort du personnage (réglage ResetOnSpawn par défaut), des boutons trop petits pour un doigt, des textes en anglais, un compteur de pièces relu à chaque image au lieu d’attendre un changement. Parfois même la logique d’achat dans le LocalScript.
Le bon prompt :
Contexte : Obby des Nuages, projet Rojo (voir CLAUDE.md). Une majorité de joueurs sont sur téléphone : l'interface doit être lisible et cliquable au doigt.
Ce qui existe déjà : le ScreenGui src/StarterGui/InterfaceObby (ResetOnSpawn = false) avec CompteurPieces et BoutonBoutique ; le RemoteEvent Remotes.AcheterObjet ; Catalogue ; les attributs du joueur Possede_<idObjet>.
Objectif : un clic sur BoutonBoutique ouvre un panneau Boutique centré qui liste chaque traînée du Catalogue (nom, prix, bouton « Acheter » ou « Possédé ») ; un bouton « Fermer » le referme.
Fichiers concernés : crée le panneau Boutique dans src/StarterGui/InterfaceObby (fichiers .model.json Rojo) et src/StarterGui/InterfaceObby/ScriptBoutique.client.luau. Ne modifie aucun script serveur.
Contraintes techniques : tailles et positions en Scale uniquement, UIAspectRatioConstraint sur le panneau, UIGridLayout pour la liste ; boutons d'au moins 12 % de la hauteur de l'écran, car on joue au doigt ; textes en français ; mise à jour par GetAttributeChangedSignal, jamais de boucle à chaque image.
Sécurité : le LocalScript ne fait qu'appeler AcheterObjet:FireServer(idObjet) ; aucun calcul de prix ni de pièces côté client.
Cas limites : Catalogue de 1 à 12 objets, écran très large ou très étroit, joueur qui meurt avec la boutique ouverte, double clic sur « Acheter ».
Vérification : selene et StyLua sans erreur ; test dans l'émulateur d'appareils de Studio (un téléphone et une tablette) : rien n'est coupé, tous les boutons sont cliquables.Ce que ça change : les contraintes « Scale », « taille minimale des boutons » et « émulateur de téléphone » transforment un vague « belle interface » en critères qu’on peut vérifier. L’interface réutilise le ScreenGui existant au lieu d’en créer un second, et reste un simple affichage : toute décision reste sur le serveur. L’interface de l’Obby est construite au module 5.
Exemple 6 : le système de manches (round system)
Les jeux à manches (une attente, une partie chronométrée, des résultats, et on recommence) sont parmi les plus demandés. Imaginons un mode « course contre la montre » pour l’Obby.
Le mauvais prompt :
fais un système de rounds
Ce que l’IA produit typiquement. Une grande boucle while true do avec des wait(10) (API dépréciée) dans un seul script ; aucun nombre minimum de joueurs ; une téléportation de tous les joueurs, y compris ceux dont le personnage n’est pas encore chargé, ce qui provoque une erreur qui arrête la boucle pour toujours ; un joueur qui quitte pendant la manche reste dans la liste des participants ; le gagnant parfois annoncé par le client. Et un affichage du temps qui ne suit pas ce que fait vraiment le serveur.
Le bon prompt :
Commence par me proposer un plan (mode plan), sans modifier de fichier.
Contexte : Obby des Nuages, projet Rojo (voir CLAUDE.md). J'ajoute un mode « course » : tous les joueurs partent ensemble, le premier arrivé gagne.
Ce qui existe déjà : la SpawnLocation du lobby, le Folder Workspace.Course avec une Part Depart et une Part Arrivee ; le RemoteEvent Remotes.Notification ; Config.
Objectif : un cycle sans fin Attente → Intermission (15 s) → Course (90 s max) → Résultats (5 s) ; le premier joueur qui touche Arrivee gagne 50 pièces ; tous les joueurs voient l'état et le temps restant.
Fichiers concernés : crée src/ServerScriptService/Manches.server.luau et src/StarterGui/InterfaceObby/AffichageManche.client.luau ; ajoute les durées et la récompense à Config. Ne modifie rien d'autre.
Contraintes techniques : un seul Script serveur qui gère l'état ; l'état et le temps restant sont publiés en attributs de ReplicatedStorage (EtatManche, TempsRestant) et le LocalScript ne fait que les afficher ; task.wait, jamais wait.
Sécurité : seul le serveur décide du gagnant (contact avec Arrivee vérifié côté serveur, participant à la manche en cours, personnage vivant) ; la récompense vient de Config.
Cas limites : moins de 2 joueurs (on reste en Attente) ; joueur sans personnage au moment du départ (il ne participe pas) ; joueur qui part pendant la course ; personne n'arrive avant la fin du temps ; deux joueurs qui touchent Arrivee presque en même temps ; une erreur pendant une manche ne doit jamais arrêter le cycle.
Vérification : selene et StyLua sans erreur ; test en serveur local à 3 joueurs : le cycle tourne trois fois de suite, un joueur qui quitte en pleine course ne bloque rien, un seul gagnant est annoncé.Ce que ça change : les états et leurs durées sont nommés, donc le code suit un schéma clair au lieu d’une boucle fourre-tout. Les cas limites listés sont précisément ceux qui cassent les systèmes de manches en vrai (personnage absent, départ en pleine partie, égalité). La consigne « une erreur ne doit jamais arrêter le cycle » pousse Claude à protéger la boucle principale.
Exemple 7 : l’anti-triche
Le mauvais prompt :
ajoute un anti-cheat
Ce que l’IA produit typiquement. Souvent un LocalScript qui surveille la vitesse du joueur et l’expulse s’il va trop vite. C’est inutile : le tricheur contrôle son propre client et supprime ce script. Ou un Script serveur qui vérifie seulement la propriété WalkSpeed (les tricheurs déplacent leur personnage autrement) et qui expulse au premier écart : un joueur honnête qui a du lag ou qui vient d’être téléporté au checkpoint se fait éjecter.
Le bon prompt :
Commence par me proposer un plan (mode plan), sans modifier de fichier.
Contexte : Obby des Nuages, projet Rojo (voir CLAUDE.md). Je veux empêcher qu'un tricheur saute des étapes en se téléportant.
Ce qui existe déjà : Progression.server.luau valide un checkpoint dès que le joueur touche la Part Workspace.Checkpoints["N"] et met à jour leaderstats.Etape, sans aucun autre contrôle ; le module serveur Validation (estAPortee).
Objectif : un joueur ne peut valider que l'étape suivante (Etape + 1), et seulement s'il est réellement près du checkpoint ; un passage anormalement rapide est signalé ; les joueurs honnêtes ne remarquent rien.
Fichiers concernés : modifie uniquement Progression.server.luau, avec en tête du script les constantes DISTANCE_MAX = 15 (studs) et TEMPS_MIN_ETAPE = 2 (secondes).
Contraintes techniques : tout se passe sur le serveur, aucun LocalScript d'anti-triche ; la distance est mesurée côté serveur avec Validation.estAPortee(player, checkpoint.Position, DISTANCE_MAX).
Sécurité : refuser sans rien valider une étape qui n'est pas Etape + 1 ou un joueur à plus de DISTANCE_MAX studs du checkpoint. Une étape atteinte moins de TEMPS_MIN_ETAPE secondes après la précédente est seulement signalée par un warn dans l'Output (nom du joueur, étape, durée) : jamais de refus ni d'expulsion pour ce motif, car un joueur rapide ou du lag peut le provoquer.
Cas limites : réapparition légitime au checkpoint après une mort, lag réseau, joueur qui retouche un ancien checkpoint, joueur sans personnage, table des derniers passages vidée quand le joueur part.
Vérification : selene et StyLua sans erreur ; en Play, un parcours normal valide toutes les étapes sans aucun warn ; depuis l'étape 2, déplacer mon personnage à la main jusqu'au checkpoint 6 (outil Move de Studio, en vue client pendant le test) ne valide rien : Etape reste à 2.Ce que ça change : la protection est sur le serveur, là où le tricheur n’a pas la main, et elle porte sur ce qui compte vraiment dans un obby (l’ordre des étapes, la distance, le temps), avec des seuils chiffrés. La consigne « signaler sans punir » pour le temps, l’absence d’expulsion et les cas limites (réapparition, lag) évitent de punir les joueurs honnêtes. Le test prouve à la fois que le jeu normal passe et que la triche est bloquée. C’est le système construit au module 4.
Exemple 8 : le classement mondial (leaderboard)
Le mauvais prompt :
fais un leaderboard
Ce que l’IA produit typiquement. Soit elle recrée des leaderstats, le petit tableau en haut à droite que ton jeu a déjà ; soit elle fait un classement global avec un OrderedDataStore mais lit le classement toutes les secondes et écrit à chaque pièce ramassée, ce qui dépasse vite les limites de requêtes des DataStores. Aucun pcall, des identifiants (UserId) affichés à la place des noms, et une récupération des noms qui peut, elle aussi, échouer sans protection.
Le bon prompt :
Contexte : Obby des Nuages, projet Rojo (voir CLAUDE.md). Je veux un panneau dans le lobby qui montre les 10 joueurs les plus avancés de tous les serveurs.
Ce qui existe déjà : les leaderstats Etape et Pieces (à ne pas modifier) ; DonneesJoueurs.luau ; une Part Workspace.Classement avec un SurfaceGui vide nommé Panneau.
Objectif : le panneau affiche le rang, le nom et la meilleure étape des 10 meilleurs joueurs, mis à jour environ toutes les 60 secondes.
Fichiers concernés : crée src/ServerScriptService/Classement.server.luau ; ne modifie rien d'autre.
Contraintes techniques : OrderedDataStore "ClassementEtapes_v1", clé "Joueur_" .. UserId, valeur = meilleure étape (nombre entier) ; écriture à PlayerRemoving et toutes les 5 minutes avec UpdateAsync, qui ne garde que la plus grande valeur (renvoie nil si la nouvelle n'est pas plus grande) ; lecture avec GetSortedAsync(false, 10) ; noms avec Players:GetNameFromUserIdAsync, mis en cache.
Sécurité : seul le serveur écrit ; la valeur vient de leaderstats.Etape côté serveur.
Cas limites : chaque appel DataStore et GetNameFromUserIdAsync dans un pcall (en cas d'échec, on garde l'affichage précédent) ; classement vide ; moins de 10 joueurs ; nom introuvable (afficher « Joueur inconnu »).
Vérification : selene et StyLua sans erreur ; explique-moi le test en Play avec l'accès aux API activé sur une copie de test, et les messages attendus dans l'Output à chaque mise à jour.Ce que ça change : le prompt sépare clairement le classement mondial des leaderstats déjà présents, fixe des fréquences raisonnables de lecture et d’écriture, et impose la protection de chaque appel réseau avec un comportement de secours (« on garde l’affichage précédent »). Résultat : un panneau qui reste juste quand le service a un raté, au lieu d’un script qui plante.
Les erreurs qui sabotent un bon prompt
D’autres pièges fréquents :
- Des adjectifs à la place de critères : « propre », « optimisé », « sécurisé », « professionnel » ne disent rien de vérifiable. Remplace-les par des règles (« un achat par seconde maximum ») et des chiffres.
- Des contraintes qui se contredisent : « ne modifie que Boutique.server.luau » et « sauvegarde l’inventaire » alors que la sauvegarde est dans
DonneesJoueurs.luau. Claude devra choisir laquelle ignorer. Relis ton prompt avant d’envoyer. - Un secret dans le prompt : jamais de mot de passe, de cookie
.ROBLOSECURITYou de clé d’API, même « pour l’exemple ». - Oublier le contexte du bug : pour corriger une erreur, colle l’erreur complète de l’Output et dis ce que tu attendais. C’est le sujet de la leçon 7.
Même un excellent prompt ne dispense pas de relire
Un prompt précis réduit fortement les erreurs ; il ne les supprime pas. L’IA peut encore inventer une API qui n’existe pas, oublier un des cas limites que tu as listés, ou écrire une vérification qui a l’air juste mais ne l’est pas. Les bonnes pratiques d’Anthropic ont une formule pour ça : « If you can’t verify it, don’t ship it » (si tu ne peux pas le vérifier, ne le publie pas).
Ta méthode : chaque rubrique de ton prompt devient une question de relecture. As-tu demandé que le prix vienne du Catalogue ? Cherche dans le code où le prix est lu. As-tu listé « joueur qui part » ? Trouve la ligne qui le gère. C’est exactement ce que tu apprendras à faire, méthodiquement, dans la leçon 6. Et pour ne pas repartir de zéro à chaque système, la bibliothèque de prompts rassemble des prompts prêts à adapter.
Ce qu’il faut retenir
- Une IA comble chaque information manquante avec la solution la plus générique : un prompt vague donne du code vague, qui ne s’emboîte pas dans ton jeu.
- Le gabarit en huit rubriques : Contexte, Ce qui existe déjà, Objectif, Fichiers concernés, Contraintes techniques, Sécurité, Cas limites, Vérification. « Objectif » et « Vérification » sont obligatoires.
- Les rubriques Sécurité et Cas limites évitent les défauts typiques : prix envoyé par le client, pas de
pcall, joueur qui part, double clic, triche côté client. - La Vérification donne à Claude un contrôle qu’il peut lancer (selene, StyLua, test décrit) et à toi un test précis dans Studio.
- Le CLAUDE.md porte les règles générales ; le prompt, ce qui est propre à la tâche. Et même un excellent prompt ne dispense jamais de relire le code.
Sources et pour aller plus loin
- Best practices for Claude Code — Provide specific context in your prompts
- Prompting best practices — Be clear and direct (Claude API Docs)
- Data stores — guide Roblox
- OrderedDataStore — documentation Roblox
- Remote events and callbacks — guide Roblox
- WorldRoot:GetPartBoundsInBox — documentation Roblox
- Animator:LoadAnimation — documentation Roblox