About scenario-roblox-expert
A Roblox Studio skill for agents and technical directors that defines an evidence-based build, test, and review protocol. It combines performance budgets, offline checks, playtests, visual inspection, and a shared Python toolkit for Roblox work.
Key features
- 01Evidence-based build and review loop
- 02Studio MCP and playtest procedures
- 03Offline Luau checks and tests
- 04Frame-time and visual quality gates
- 05Shared Python toolkit for Roblox skills
Use cases
- 01Planning a Roblox feature around device budgets
- 02Checking scripts before opening Studio
- 03Measuring server and client frame times
- 04Reviewing visual changes with captures
- 05Delivering audited Roblox Studio changes
MCP support
Uses Studio's built-in MCP server through the shared toolkit to inspect, edit, and test Roblox places. It emphasizes checking parsed results rather than relying on tool text.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenario-roblox-expertAfter installing, type/scenario-roblox-expertin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/critique.mdexpert-notes.mdgui-paths.mdprocedures.mdsources.mdtraps.mdscripts/rbx_check.pyrbx_env.pyrbx_mcp.pyrbx_play.pyrbx_review.pyrbx_run.pyscripts/agentkit/Audit.luaulune_audit.luauSpec.luauStats.luauDetails
From the project READMERoblox expert (technical director and agent protocol)
Target: Roblox Studio (October 2026 build) on macOS, driven through the scenario-roblox-expert toolkit.
Expert Roblox work is a loop run against evidence: set the budget, build one stage, measure it, look
at it, fix it, advance. An agent without a mouse gets there through Studio's built-in MCP server,
offline checks and text files, passing only on a parsed result and never on one signal alone. This
skill is that protocol, the shared toolkit every scenario-roblox-* skill calls
(scripts/), and the map of the team. 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.
Status (2026-10-18): the toolkit ran live in Studio 0.743, 36 live tests and 16 offline tests,
all pass (references/procedures.md). run_headless is live run
pending: it needs a machine with no interactive Studio.
Stance (the expert delta)
- Pass on a parsed result, never on tool text.
execute_luaureturns only the first return value, and prints go to the Output log (observed).rbx_run.runreturnsok,result,error,outputwith your chunk's line numbers. "Audit the results" of anything an agent made over the MCP (Roblox IAS Q&A, KnFJl6z5UN0 [00:21:23]). - One place per agent, opened once, never closed by an agent. Never run code in a window you did not open; the server asks you to confirm with the user before modifying their game. A SIGTERM to a place hung its window and hid every Studio from the server (observed). Closing places belongs to the orchestrator, with the user's agreement.
- Frame time against a budget, on the target. Consistent frame time beats average FPS (Andrew, XLCZfGEm9tU [00:02:12]). Triage: works, real device, frame rate, Stats, then MicroProfiler (McNeill, rwLEnBtXr30 [00:29:19]). Studio memory "is incorrect" (McNeill, cWDNVvOh4o0 [00:28:25]): it read 3,800 MB in a small playtest here.
- Look AND check. Every visual change gets captures from fixed cameras,
image_checks, a before and aftercompare, and a contact sheet you open. Feel cannot be automated (RDC 2026, vumAuOqSmNA [00:29:36]); AI "doesn't automatically have taste" (SmartyRBX, da-wHJpNhBo [00:07:35]). - Small steps, red before green. One feature per prompt (SmartyRBX, 0ENeVVC9QT0 [00:10:26]).
Follow a working answer with a security review, and test death and respawn (SimTek, -0H6JRtrA2Y
[00:03:53, 00:06:42]). Write the
Spectest first and watch it fail. - Text files, Git, and a gate before Studio. A CI gate on type and lint errors catches "90
percent of what breaks a game" (CJ_Oyer, gapG2tH-etI [00:12:50]); Git is the safety net (Roblox
Learn, v8r1d80DxOY [00:00:45]). Gate with
rbx_check(luau-lsp with Roblox definitions), not bareluau-analyze. - Know what persists. An Edit change lives in the open session until someone saves, and no MCP
tool saves; Server writes vanish when play stops (L24, L25). A Rojo reconnect deletes Studio-only
instances without
$ignoreUnknownInstances(Distracted Games, Zhl_NAPMztY [00:44:28]). - Be honest about GUI-only tools. The Animation Editor, terrain brushes, Avatar Setup, the
Accessory Fitting Tool, Creator Hub dashboards and publishing are GUI steps. Give the substitute
(
Terrain:WriteVoxels,KeyframeSequenceProvider:RegisterKeyframeSequence,AvatarCreationService:ValidateUGCAccessoryAsync) or the GUI path (references/gui-paths.md). Never publish, upload or spend Robux without the user's go.
Channels
| Channel | Use for | Cost (measured) | Rules |
|---|---|---|---|
Studio MCP, Edit (rbx_run.run) | build, audit, inspect, edit scripts | 67 ms per call; first open 18.3 s | your own place; chunks yield every 1,000 items |
Playtest (rbx_play.Playtest) | Server and Client code, keys, walking, mouse, play captures | ready in 4.2 s | one server, one client; stops even after an exception |
Offline (rbx_check, Lune, lune_audit) | gate before Studio, CI, pure-logic tests | seconds | Lune has no physics, no network |
| Files (Rojo, Script Sync) | durable code in Git | rojo build 0.04 s | never rebuild a .rbxl that Studio holds open |
Headless RunScript (rbx_run.run_headless) | CI with no interactive Studio | about 6 s per run | refuses while any Studio runs |
| Open Cloud Luau Execution | CI assertions on a published place | 5 tasks per minute | API key and the user's go; no physics, nothing persists |
Concurrency: at most RBX_MAX (default 3) agents hold a place through
rbx_env.place_slot(). Each place is its own Studio process of about 2.9 GB,
and every window renders all the time. open_place waits while RBX_MAX_PLACES (default 4) place
windows are open machine-wide, GPU use or GPU memory is above 85%, or RAM is above 75%. Close a
place as soon as its run ends. Several StudioMCP proxies share one port, so Studio.call retries
for 45 s while the host proxy changes.
Establish first
Ask once, then run:
- target devices and the lowest one (default: phone, 60 fps, 16.67 ms);
- players per server and genre;
- Rojo, Script Sync or Studio-only;
- whether Server Authority or streaming is wanted (
StreamingEnabledis on in new places); - which place is the agent's own, and the deliverable.
Then rbx_env.doctor() and an Audit.all(game) of the starting place. Write the budget, sync tool,
data model plan and owners into the plan.
The expert loop
- Brief in numbers. Budgets per device, players, memory, art style. GATE: every later check has a number.
- Place.
new_placethenopen_placeinsideplace_slot, once. GATE:open_places()lists it;Audittally has no error.new_placeoverwrites<dir>/default.project.json:diris a new folder of your own, never the user's project.- Existing game: ask the user to save first, since edits in their open window are not on disk.
Merge a copy of the saved place with the code build (scenario-roblox-tooling P15,
merge_place, any repository) and open the merge, never the user's file. - Edit a worktree or copy of their sources: their
rojo servesyncssrc/into their window.
- Baseline.
Audit,Spectests that fail first, captures from fixed cameras,Stats.framesin a playtest. GATE: numbers recorded, sheet opened. - Build one stage with idempotent chunks or files; route domain work to its owner (below). Adding instances to the open place: procedures.md P2.
- Measure.
rbx_check0 errors,Specall green,Auditclean,Stats.frameson Server and Client within the frame gate: p95 at most 1.2 x the frame budget (20 ms at 60 fps) and worst at most 2 x (33.3 ms). Heartbeat steps jitter around the vsync interval, so a healthy 60 fps place reads p95 17.2 to 17.4 ms and a bare 16.67 ms gate fails it; a dropped frame reads about 33 ms. - Look AND check. Same cameras,
image_checksok,comparechanged where it should, contact sheet opened, judged with the owner's critique. - Fix before advancing, one change at a time.
- Deliver with evidence: Verified (what ran, result file, numbers, frames) and Assumed (not run). List what a human must still play for feel.
Toolkit (scripts/, the contract for every scenario-roblox-* skill)
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1].parent / "scenario-roblox-expert" / "scripts"))
| Module | Main calls | Returns |
|---|---|---|
rbx_env | doctor(), new_place(dir, name, template, src), open_place(path), open_places(), place_slot(), machine_load(), wait_for_gpu() | dict; Path; bound Studio; [{id, name}]; slot index; percents |
rbx_mcp | Studio(place=...): .call(tool, args), .luau(code, dm), .capture(png, extra), .studios(), .tools() | MCP result dict; text; Path |
rbx_run | run(studio, code, dm="Edit", kit=[...]), run_headless(place, code, kit) | {ok, result, error, output} |
rbx_play | Playtest(studio): .server(code, kit), .client(code, kit), .wait_for, .press(key, hold), .walk_to([x, y, z]), .keys, .mouse, .console, .capture, .mode | rbx_run dicts; tool text |
rbx_check | check(paths, code, sourcemap, selene, stylua, solver="both") | {ok, errors, warnings, tools}, findings tagged old or new solver |
rbx_review | image_checks(png), compare(a, b), contact_sheet(pngs, out) | flags and ok; changed_fraction; path |
| AgentKit | Audit.all(game), Stats.snapshot(), Stats.frames(s), Stats.diff, Spec.new() | findings and tally; numbers; {passed, failed, total} |
Full signatures, return shapes and tool arguments:
references/procedures.md section 1. Domain code goes in each sibling's
scripts/rbx_<domain>.py and scripts/agentkit/<domain>/. rbx_env.close_place is
orchestrator-only and refuses by default.
Numbers
| Value | Relative to | Source |
|---|---|---|
| 16.67 / 33.33 ms | frame at 60 / 30 fps | Andrew [00:02:12] |
| 67 ms | one execute_luau round trip, Edit | observed, L9 |
| 4.2 s / 18.3 s | playtest ready / first open_place | observed, L13, L3 |
| 60 fps, p95 17.5 ms | empty test place, Server and Client in Studio | observed, L16, L17 |
| p95 20 ms, worst 33.3 ms | frame gate at 60 fps (1.2 x and 2 x the budget) | derived from L16, L17 |
| 1.3 GB | client memory target on a 2 GB phone | McNeill [00:27:53] |
| about 1,000 | items per chunk before a task.wait() | tool description |
| 20,000 / 4,000 | triangles per MeshPart / per accessory | specifications pages |
| 4096 px | max texture side in places (accessories 2048) | devforum 4316229 |
| 500 per s, 1,000 bytes | client remote requests; UnreliableRemoteEvent payload | RemoteEvent reference |
Quality gates
- Measurable:
rbx_check0 errors;Specfailed == 0andtotal > 0, after one red run;Audittally.error == 0and deprecated APIs gone;Stats.framesp95 and worst within budget; console free of errors after a playtest;get_studio_stateback in Edit. - Visual: every capture passes
image_checks; before and after from the same cameras; a contact sheet of at most 1,600 px opened and judged.
Common mistakes
| Mistake | Symptom | Fix |
|---|---|---|
no studio_id, or the wrong window | edits land in the user's game | Studio(place=...) binds by exact name |
| trusting tool text or exit codes | prints missing; RunScript exits 0 on errors | rbx_run payload |
| playtest never stopped | Edit data model "not available in Play mode" | with Playtest(s); check mode() |
"Position" in a Rojo project | part at the origin, build exit 0 | CFrame (12 numbers); new_place converts |
script_grep with [ or ( | "No matches found" | escape with % |
| long chunk without yields | Studio frozen, MCP drops | batches of 1,000 with task.wait() |
| gating on Studio memory or FPS | numbers do not match devices | Stats.frames p95; device tests |
| claiming multi-client tests | one MCP playtest is one client | StudioTestService (not yet run here) |
| GUI scripting or SIGTERM to Studio | hung windows, server lists nothing | ask the user; never signal |
Routing and handoffs
| Skill | Route here when the brief mentions |
|---|---|
| scenario-roblox-architecture | project layout, client and server split, RunContext, services, ModuleScripts, frameworks, Attributes and tags |
| scenario-roblox-luau | task library, deferred events, types, OOP, buffers, native code, Parallel Luau, memory leaks in code |
| scenario-roblox-networking | remotes, "exploiters", "remote spam", validation, rate limits, network ownership, Server Authority |
| scenario-roblox-data | "data not saving", DataStores, ProfileStore, migration, MemoryStore, MessagingService, teleports |
| scenario-roblox-environment | building, terrain, materials, MaterialVariant, lighting, Atmosphere, sky, graybox, per-model streaming |
| scenario-roblox-vfx-audio | particles, beams, trails, flipbooks, hit effects, Audio API, sounds, music |
| scenario-roblox-ui | HUD, menus, "UI scales wrong on phone", gamepad, Input Action System, localization |
| scenario-roblox-animation | Animator, tracks, rigs, Blender animation import, IK, faces, character controllers |
| scenario-roblox-gameplay | combat, hitboxes, raycasts, projectiles, "NPC pathfinding stuck", game loops, vehicles |
| scenario-roblox-monetization | passes, Developer Products, ProcessReceipt, subscriptions, ads, analytics, live ops |
| scenario-roblox-assets | "import from Blender", meshes, textures, SurfaceAppearance, "mesh fails validation", UGC |
| scenario-roblox-tooling | Rojo, Wally, Rokit, Selene, StyLua, Lune, CI, Open Cloud, plugins, Script Sync |
| scenario-roblox-performance | "my game lags", MicroProfiler, memory, streaming settings, mobile, publishing |
Split items have one owner, which names the second skill in its packet:
- streaming: settings to performance, per-model modes to environment;
- hit validation: the rule to networking, the casts to gameplay;
- ProcessReceipt: the contract to monetization, persistence to data;
- AI meshes: intake to assets, placement to environment, reuse budgets to performance;
- Server Authority: enabling to networking,
BindToSimulationlogic to gameplay; - memory leaks: diagnosis to performance, cleanup code to luau.
Every handoff packet carries: place path and window, channel, the brief in numbers, toolkit calls, evidence paths (results JSON, Audit tally, Spec totals, Stats, contact sheet), Verified, Assumed, open issues, and a fence. The receiver reproduces one piece of evidence before building on it.
Scenario assets into Roblox
| Asset | Generated with | Lands in |
|---|---|---|
| meshes, props, characters | scenario-3d | scenario-roblox-assets (import, 20,000 triangles), then environment |
| PBR maps, tileable materials | scenario-textures | scenario-roblox-assets (SurfaceAppearance), scenario-roblox-environment (MaterialVariant) |
| skyboxes | scenario-skyboxes | scenario-roblox-environment (Sky, six faces) |
| music, SFX, ambience, voice | scenario-audio | scenario-roblox-vfx-audio (under 20 MB, 7 minutes, 48 kHz) |
| icons, item art, UI pieces, decals | scenario-game-assets | scenario-roblox-ui, scenario-roblox-environment |
| flipbook frames | scenario-sprite-animation | scenario-roblox-vfx-audio (2x2, 4x4 or 8x8 grids) |
Check each asset before import (scenario-quality-gate) and after, in a capture judged by the receiver. Getting any file into a shipped game uploads it to Roblox, so it needs the user's go.
Traps
Read references/traps.md before the first job: the 2026 API renames and
replacements, the Studio MCP tool facts (is_start, capture_id, datamodel_type "Client", lost
replies), process hazards, Rojo and Script Sync traps, and live corrections to the version deltas.
References
references/procedures.md: the toolkit API in full, chunk rules, and each procedure with its tests and results.references/traps.md: current-Roblox traps, MCP and file pipeline traps, live corrections.references/expert-notes.md: expert claims by source with timestamps.references/critique.md: the rubric for judging an agent's Roblox work and a handoff.references/gui-paths.md: Studio menus for the same tasks, and the GUI-only steps.references/sources.md: every source with credentials, URLs and best timestamps.