Draft preview · this lesson is still being technically validated. Browser experiments and reference material remain available; trusted completion is enabled after publication.
v2 · Activity 4 of 4 · rate-limits · duplicate-rewards · idempotency · 28 min Requires Roblox Studio
A rate limit bounds how often a request is processed. A claim record prevents a specific reward from being granted again even after the rate window passes. You need both.
In this temporary example, claimed and coins are server-owned. The callback sets claimed before adding the fixed amount, with no yield between those state changes.
Implement a temporary first-visit claim at RewardPad: fixed ID first-visit, nearby live player, once per session, 10 practice coins, and a 0.5-second request interval. Set the claim before the award without yielding.
Interface: Complete the documented Studio hierarchy and self-check the behavior outside the browser.
| Input | Expected |
|---|
if data.claimed then return end
data.claimed = true
data.coins += 10The state transition records the claim before the reward. No client PASS string or printed value can replace it.
New syntax: rate-limits, duplicate-rewards, idempotency
Your explanation is a self-check. Exact wording is not automatically graded.
Self-checked Studio exercise. A saved checklist records your own review; no uploaded place file or screenshot is executed here.
Workspace/RewardPad (anchored Part) ReplicatedStorage/ClaimVisit (RemoteEvent) ServerScriptService/VisitRewards (Script, RunContext=Server) StarterPlayer/StarterPlayerScripts/ClaimTest (LocalScript)
Main guided reference implementation; use the hierarchy’s stated Script or ModuleScript type.
local Players = game:GetService("Players")
local request = game:GetService("ReplicatedStorage"):WaitForChild("ClaimVisit")
local pad = game:GetService("Workspace"):WaitForChild("RewardPad")
local records = {}
local function initialize(player) records[player] = { claimed = false, coins = 0, nextAllowed = 0 }; player:SetAttribute("PracticeCoins", 0) end
Players.PlayerAdded:Connect(initialize)
for _, player in ipairs(Players:GetPlayers()) do initialize(player) end
Players.PlayerRemoving:Connect(function(player) records[player] = nil end)
request.OnServerEvent:Connect(function(player, rewardId)
local data = records[player]
if not data then return end
local now = os.clock()
if now < data.nextAllowed then return end
data.nextAllowed = now + 0.5
if type(rewardId) ~= "string" or rewardId ~= "first-visit" or data.claimed then return end
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not root or not root:IsA("BasePart") or not humanoid or humanoid.Health <= 0 then return end
if (root.Position - pad.Position).Magnitude > 10 then return end
data.claimed = true
data.coins += 10
player:SetAttribute("PracticeCoins", data.coins)
print("First visit granted", player.UserId)
end)Controlled first claim and delayed duplicate; inspect server PracticeCoins.
local remote = game:GetService("ReplicatedStorage"):WaitForChild("ClaimVisit")
local player = game:GetService("Players").LocalPlayer
if not player.Character then player.CharacterAdded:Wait() end
task.wait(2)
remote:FireServer("first-visit")
task.wait(1)
remote:FireServer("first-visit")Do not rely only on the cooldown; test a repeat after it expires.
Avoid task.wait or storage calls between checking claimed and setting it in this local state transition.
This is a bounded learning lab. It has not been executed in an approved Roblox Studio environment by this website.
A checked checklist records your own review, not automated project competence or a community verification.
A rate limit bounds how often a request is processed. A claim record prevents a specific reward from being granted again even after the rate window passes. You need both.
In this temporary example, claimed and coins are server-owned. The callback sets claimed before adding the fixed amount, with no yield between those state changes.
A session-only record is not durable idempotency. Rejoining resets this learning lab. Persistent rewards need the claim and award recorded together under a reviewed ownership and storage design.
Replace this code with the starter? Download your draft to keep a copy.
Edit and run these files in Roblox Studio. This browser has no Roblox services, physics, or replication.
Your output will appear here.
Sign in with RoCreators to save private progress across devices. Community verification is separate.