scenario-roblox-expert
Use when an agent builds, fixes or tests anything in Roblox Studio: "make a Roblox game", "Roblox Studio", a Luau script, "my Roblox game lags", agent control of Studio, the Studio MCP server (execute_luau, start_stop_play, screen_capture), Rojo or Script Sync projects, offline Luau checks, playtests driven by code; when the MCP server lists no Studio, a playtest hangs, or 2019 to 2023 Roblox code breaks; or when a Roblox brief (UI, data, networking, combat, monetization, assets, performance) must go to the right scenario-roblox-* specialist.
Try your first task
A source-grounded starter task has not been verified for this skill yet.
SKILL.md
name: scenario-roblox-expertdescription: "Use when an agent builds, fixes or tests anything in Roblox Studio: \"make a Roblox game\", \"Roblox Studio\", a Luau script, \"my Roblox game lags\", agent control of Studio, the Studio MCP server (execute_luau, start_stop_play, screen_capture), Rojo or Script Sync projects, offline Luau checks, playtests driven by code; when the MCP server lists no Studio, a playtest hangs, or 2019 to 2023 Roblox code breaks; or when a Roblox brief (UI, data, networking, combat, monetization, assets, performance) must go to the right scenario-roblox-* specialist."license: MITRoblox 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, load it with skill_get (Scenario MCP) or ask the user to install it
(https://github.com/scenario-labs/skills#install); 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.