Skip to content
cccskills
FreePublished on GitHub

scenario-roblox-data

Build and audit Roblox player data systems for saving, purchases, trading, and migrations. Check session locks, retries, budgets, and crash recovery with simulated stores and servers, then identify what still requires testing in a published experience.

OriginalShow the original description

Use when a Roblox game saves or loads player data, or when players report data not saving, lost progress, rollbacks, duplicated items or a purchase granted twice. Triggers: DataStoreService, UpdateAsync, SetAsync, BindToClose, ProfileStore, ProfileService, DataStore2, session locking, data schema or migration, Developer Product ProcessReceipt, player trading, OrderedDataStore leaderboards, MemoryStore, MessagingService, TeleportService and reserved servers, DataStore budgets and throttling (errors 301 to 306), 'You must publish this place to the web to access DataStore', and testing data code in an unpublished place.

scenario-labs/skills1,179 stars(GitHub 1,179 + cccskills 0)

Run score Not tested

See how to install

Good for

  • Investigating lost progress or data rollbacks
  • Preventing duplicate item grants or purchase processing
  • Planning a player data schema migration
  • Testing trades across simulated server crashes
  • Checking DataStore budgets and throttling

Files included (18)

  • SKILL.md14.6 KB
  • references/critique.md3.8 KB
  • references/expert-notes.md14.0 KB
  • references/gui-paths.md4.8 KB
  • references/procedures.md32.4 KB
  • references/sources.md7.8 KB
  • scripts/agentkit/data/DataAudit.luau8.6 KB
  • scripts/agentkit/data/DataCheck.luau6.0 KB
  • scripts/agentkit/data/KeyQueue.luau5.7 KB
  • scripts/agentkit/data/lune_data_audit.luau562 B
  • scripts/agentkit/data/Migrate.luau2.0 KB
  • scripts/agentkit/data/MockDataStore.luau26.4 KB
  • scripts/agentkit/data/PlayerData.luau6.1 KB
  • scripts/agentkit/data/Receipts.luau5.6 KB
  • scripts/agentkit/data/SafeTeleport.luau4.9 KB
  • scripts/agentkit/data/ServerSim.luau7.5 KB
  • scripts/agentkit/data/Trade.luau7.0 KB
  • scripts/rbx_data.py14.9 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Roblox Data and Backend

Roblox Studio (October 2026 build) on macOS, driven through the scenario-roblox-expert toolkit.

Expert level here means the data layer stays correct when a server crashes, a write fails after it landed, or two servers want the same player. Prove those cases against a simulated multi-server backend before any published test, because an unpublished place cannot reach DataStores at all.

If a sibling skill named here is missing from your available skills, ask the user to install it (npx skills add scenario-labs/skills --skill <name>); unattended, proceed from tool schemas and flag the gap.

REQUIRED BACKGROUND: scenario-roblox-expert (channels, review loop, toolkit, current-Roblox traps).

Stance (the expert delta)

  1. Session locks stop duplication, UpdateAsync alone does not. One ProfileStore profile per player holds the lock (loleris, ProfileStore docs). UpdateAsync is safer than SetAsync only when the transform reads the old value (Suphi Kaner, 00:04:43).
  2. A failed write may have landed. Read with UseCache = false before redoing it (Roblox best practices). Put an operation id inside the value so a repeat is a no-op [added]. With 30% of writes failing after they landed, naive retries stored 39 for 30 operations in the mock run.
  3. Retries on one key stay in order. A late retry of an old value overwrites newer data (Roblox best practices). KeyQueue serializes per key.
  4. A purchase is granted once and confirmed saved. Log each PurchaseId in the profile. Return PurchaseGranted only when LastSavedData holds it (Rileybytes, 00:29:08; Roblox purchasing doc). Every other path returns NotProcessedYet.
  5. Migrate before Reconcile. Reconcile copies the template's SchemaVersion into old data, and the migration is skipped [added, proven against the real ProfileStore].
  6. Old servers never save newer data. During an update, old and new servers run side by side. Refuse a higher SchemaVersion, release the lock untouched, and kick [added]. Publish every place of a multi-place game together (CJ_Oyer, 00:27:33).
  7. Two profiles never save atomically. Editing both without a yield protects memory only (loleris). Escrow both sides, then let one trade-log record decide [added]. Without escrow a crash between the two saves duplicated a Shield and lost a Sword.
  8. Test against an injected store. Write the data code so DataStoreService can be swapped for MockDataStore and run several simulated servers with the unmodified ProfileStore [added].

Establish first

  • Published, and Studio API access on? Unpublished: every DataStore call throws, so test with mocks. Published with access on: Studio writes live keys, so use a separate test experience (Roblox docs) or ProfileStore.Mock.
  • What persists where. Player state goes in one profile. Leaderboards use an OrderedDataStore. Matchmaking and short-lived shared state use MemoryStore. Announcements use MessagingService.
  • Trading, Developer Products, multi-place? Each adds a stage below.
  • Peak CCU, players per server, profile size. Feed rbx_data.budget.
  • Data already live? Then keys and store names are frozen; every change becomes a migration step.

Defaults: ProfileStore at the pinned commit, store PlayerData, key Player_<UserId>, a SchemaVersion field, a 100-entry PurchaseIds log.

Procedure

Modules live in scripts/agentkit/data/ and have no requires. Inline them with rbx_data.run(studio, code, dm, data=[...]), or build them into a place with rbx_data.build_place(dir). Full code and results: procedures.md.

  1. Probe the environment. Goal: know which services answer here. How: rbx_data.probe_services(studio, "Edit"), then "Server" in a playtest. GATE: the error texts are recorded, and the store choice (real or mock) follows from them.
  2. Design the schema. Goal: a profile that saves as-is and grows safely. How: DataCheck.report(template) and a maxed sample, DataCheck.key(key, {userId}). GATE: zero error findings, size under 25% of 4,194,304 characters, and a JSON round trip equal to the input.
  3. Plan the budget. Goal: no throttling at peak. How: rbx_data.budget(players, ccu, kb, saves_per_min). GATE: ok is true at peak CCU, and the per-key writes per minute cover autosave plus purchases.
  4. Wire sessions. Goal: one owner per profile. How: PlayerData runs StartSessionAsync with Cancel, kicks on nil, then AddUserId, migrate, Reconcile, a kick on OnSessionEnd, and EndSession on leave. GATE: the sessions suite passes, and a playtest shows the profile loaded with DataStoreState "NoAccess".
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local ProfileStore = require(ServerStorage.Packages.ProfileStore)
local PlayerData = require(ServerStorage.DataKit.PlayerData)
local Migrate = require(ServerStorage.DataKit.Migrate)

local data = PlayerData.new({
	ProfileStore = ProfileStore,
	Players = Players,
	storeName = "PlayerData",
	template = { SchemaVersion = 2, Coins = 0, Inventory = {}, PurchaseIds = {} },
	migrate = Migrate.new({
		latest = 2,
		steps = {
			-- v1 kept owned items as an array of names; v2 counts them by id.
			[2] = function(d: any)
				d.Inventory = d.Inventory or {}
				for _, item in ipairs(d.Items or {}) do
					d.Inventory[item] = (d.Inventory[item] or 0) + 1
				end
				d.Items = nil
			end,
		},
	}),
})
data:Start()
  1. Version the schema. Goal: old data upgrades once. How: Migrate.new{latest, steps}, one fixture per old version. GATE: the schema suite passes, including the Reconcile trap and the "newer" refusal.
  2. Grant purchases. Goal: exactly one grant per PurchaseId. How: Receipts.new{getProfile, isPresent, handlers}; assign ProcessReceipt in one script. GATE: the receipts suite passes with ghost writes, crashes and server changes.
  3. Raw stores. Goal: leaderboards, counters and logs that survive retries. How: KeyQueue.new(store) with an op id in the value and a verify function. GATE: the keyqueue suite passes.
  4. Trades. Goal: no item created or lost by a crash. How: Trade.paidTradingAllowed for both players (PolicyService IsPaidItemTradingAllowed, fail closed), Trade.new{log = KeyQueue.new(tradeLogStore)}, trade:Recover(profile) in onLoaded. GATE: the trade suite passes at every crash stage.
  5. Teleports. Goal: parties arrive together. How: SafeTeleport.new{TeleportService}, TeleportParty for groups. GATE: the teleport suite passes against the fake service; a real teleport needs a published experience.
  6. Audit. Goal: no known mistake left in the code. How: DataAudit.place(game) in Edit, or rbx_data.py audit place.rbxl offline. GATE: zero error findings outside test fixtures.
  7. Operate. Rollbacks with PlayerData:Restore (ProfileStore VersionQuery), final edits in OnLastSave, RTBF templates with rbx_data.rtbf_config: procedures 13 to 15.
  8. Prove and report. Run the offline suites, then the same suites in Studio Edit through rbx_run, then the playtest checks. List what still needs a published place.

Numbers

ValueRelative to
Server read or write: 60 + 40 x players per minutePer server, default, tunable
Experience read 300 + 40 x CCU, write 300 + 20 x CCU per minuteWhole experience, shared with Open Cloud
UpdateAsync, so every ProfileStore save, spends 1 read and 1 writeBoth budgets
Per key: write 4 MB per minute, read 25 MB per minuteRounded up to whole KB per request
Value 4,194,304 characters; key, name, scope 50Per key
Queue of 30 per request type, then errors 301 to 306Per server
GetAsync cache 4 sPer data store instance
One version kept per key per UTC hour, 30 days after overwritePer key
Storage 500 MB + 1 MB x lifetime usersCompressed latest versions
ProfileStore autosave 300 s, steal 40 s, assume dead 630 sPinned source; assume dead compares os.time() across servers
ProfileStore first retry 5 s, then every 10 sWhile another server holds the lock
Profile load over 5 sTreat as a bug (loleris)
BindToClose budget 30 sPer server shutdown
MessagingService message 1 kBPer publish

Quality gates

  • run_offline.sh: all Lune suites pass, rbx_check reports zero errors in kit, place and snippets.
  • run_live.py: the same suites pass in Studio Edit, and the playtest checks pass.
  • DataAudit: zero error findings outside fixtures.
  • Console: one "[ProfileStore]: Roblox API services unavailable" line in an unpublished playtest, and no save errors.
  • Published test experience (later): two real servers hand a player over in under 5 s.

Common mistakes

MistakeSymptomFix
SetAsync on PlayerRemoving, no lockRollbacks, duplicated items after server hopsProfileStore through PlayerData
Key from player.NameData lost after a renamePlayer_<UserId>
Reconcile before migrationOld fields stranded, new fields emptyMigrate first
Retry by recalling the same writeCounters too highOp id in the value, KeyQueue
PurchaseGranted before the savePaid but not granted after a crashReceipts
Handler that yieldsGrant applied twiceHandlers run on a copy and must not yield
Two profile saves as a tradeItems duplicated or lostTrade with escrow
GetDataStore outside pcallScript errors at startup in StudioProbe inside pcall, then fall back to the mock
Awaiting MessagingService in an unpublished playtestThread hangsCall it in task.spawn
Retrying a teleport with ShouldReserveServerParty split across serversReserve once, reuse the access code
Editing a ModuleScript's Source in Edit, then requiring it againThe old code keeps runningReplace the module with a Clone first

Handoffs

  • From scenario-roblox-architecture: where the data service lives and how systems read it.
  • From scenario-roblox-monetization: product ids and amounts. Back: the Receipts handler table.
  • From scenario-roblox-networking: validated trade and shop requests. Back: Trade and inventory calls.
  • To scenario-roblox-ui and scenario-roblox-gameplay: values exposed as Attributes or signals, never raw profiles.
  • To scenario-roblox-performance: shutdown timing and update publishing.
  • To scenario-roblox-tooling: the Lune suites for CI.
  • To scenario-roblox-luau: types for the template.

Current Roblox notes

  • DataStore limits became experience-wide on 2026-07-29; old answers quote 60 + 10 x players and a 6 s cooldown, both gone.
  • Storage limits are enforced from 2026-04-09.
  • HttpService:JSONEncode encodes NaN and infinity as tagged objects since 2026-03-10, and drops sparse and mixed entries silently (live test).
  • ProfileService is unsupported; use ProfileStore. DataStore2 is legacy.
  • ReserveServerAsync replaces ReserveServer.
  • Studio API access: File > Experience Settings > Security > Enable Studio Access to API Services, published games only.
  • Studio budgets do not follow the server formulas: a one-player playtest read 585.

References

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Review unresolved GitHub pull request feedback, check each claim against the current code, fix warranted issues, and reply in the relevant threads. It can also push changes and report decisions or follow-up items, while leaving branch updates and thread resolution to explicit direction.

  • Triaging unresolved review threads on a pull request
  • Fixing valid feedback and replying with the pushed change
  • Answering reviewer questions without changing code
scenario-labs/skills1.2KUpdated Oct 28, 2026

Create a concise Conventional Commits squash message from a pull request’s net diff. The skill checks the repository’s commit rules, aligns the header with the PR title, and prepares the description for copying and display.

  • Preparing a squash message from a pull request’s changes
  • Checking a commit message against repository rules
  • Aligning a PR title with its squash commit header
scenario-labs/skills1.2KUpdated Oct 28, 2026

pr-summary

FreeSummary

Rewrite the current pull request description to reflect the branch’s net changes and the repository’s own template. The skill discovers the correct base branch and conventions, preserves relevant human and bot notes, and updates the body through GitHub’s API.

  • Refreshing a PR description after the branch changes
  • Writing a summary that follows the repository template
  • Checking which base branch a stacked PR targets
scenario-labs/skills1.2KUpdated Oct 28, 2026

scenario

FreeSummary

Connect an AI agent to Scenario through MCP and guide generation of images, video, 3D, audio, and game assets. Find suitable models, prepare prompts and reference files, track generation jobs, and check project scope, usage, or access errors.

  • Connecting Scenario MCP to an AI agent
  • Finding a model for image, video, or 3D generation
  • Uploading reference files and tracking generation jobs
scenario-labs/skills1.2KUpdated Oct 28, 2026

scenario-3d

FreeSummary

Generate 3D models from text or images through Scenario, then refine meshes, retexture, unwrap UVs, rig characters, or retarget animation. Preview results and download assets in formats such as GLB or FBX for use in 3D tools and game engines.

  • Creating a 3D prop from a concept image
  • Retexturing or remeshing an existing asset
  • Preparing a character mesh for rigging
scenario-labs/skills1.2KUpdated Oct 28, 2026

Create explorable 3D environments and Gaussian splat scenes from text, images, panoramas, multi-view photos, or walkthrough video using Scenario. Preview a draft, adjust the scene, then generate a higher-quality version and download the resulting splat asset.

  • Turning a concept into a walkable 3D environment
  • Reconstructing a scene from photos or walkthrough video
  • Converting a panorama into a navigable splat
scenario-labs/skills1.2KUpdated Oct 28, 2026

See all skills by scenario-labs

Report a problem with this skill