Catalogskills.sh · scenario-labs

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.

Review advisedVerified hub233 installs

Savant verdict: Review advised

Security analysis or source-hub review identified patterns requiring attention.

Live evaluation

Not evaluated yet. Workspaces can request a live evaluation.

Security assessment

Scan pending.

Structure

  • 2 referenced files aren't in the packagescripts/, scripts/agentkit/. They may be binary, too large to catalog, or missing upstream.
  • Ships 6 executable scriptsscripts/rbx_check.py, scripts/rbx_env.py, scripts/rbx_mcp.py, scripts/rbx_play.py, scripts/rbx_review.py, …. Review what they do before enabling the skill for agents with tool access.
  • The source hub's security checks flagged this skillSnyk: warn (Risk: MEDIUM · 1 issue)

SKILL.md

---
name: scenario-roblox-expert
description: "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: MIT
---

# Roblox 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/`](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`](references/procedures.md)). `run_headless` is live run
pending: it needs a machine with no interactive Studio.

## Stance (the expert delta)

1. **Pass on a parsed result, never on tool text.** `execute_luau` returns only the first return
   value, and prints go to the Output log (observed). [`rbx_run.run`](scripts/rbx_run.py) returns
   `ok`, `result`, `error`, `output` with your chunk's line numbers. "Audit the results" of anything
   an agent made over the MCP (Roblox IAS Q&A, KnFJl6z5UN0 [00:21:23]).
2. **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.
3. **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.
4. **Look AND check.** Every visual change gets captures from fixed cameras,
   [`image_checks`](scripts/rbx_review.py), a before and after `compare`, 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]).
5. **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 `Spec` test first and watch it fail.
6. **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`](scripts/rbx_check.py) (luau-lsp with
   Roblox definitions), not bare `luau-analyze`.
7. **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]).
8. **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`](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()`](scripts/rbx_env.py). 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 (`StreamingEnabled` is 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

1. **Brief in numbers.** Budgets per device, players, memory, art style. GATE: every later check has
   a number.
2. **Place.** `new_place` then `open_place` inside `place_slot`, once. GATE: `open_places()` lists
   it; `Audit` tally has no error.
   - `new_place` overwrites `<dir>/default.project.json`: `dir` is 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 serve` syncs `src/` into their window.
3. **Baseline.** `Audit`, `Spec` tests that fail first, captures from fixed cameras, `Stats.frames`
   in a playtest. GATE: numbers recorded, sheet opened.
4. **Build one stage** with idempotent chunks or files; route domain work to its owner (below).
   Adding instances to the open place: procedures.md P2.
5. **Measure.** `rbx_check` 0 errors, `Spec` all green, `Audit` clean, `Stats.frames` on 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.
6. **Look AND check.** Same cameras, `image_checks` ok, `compare` changed where it should, contact
   sheet opened, judged with the owner's critique.
7. **Fix before advancing,** one change at a time.
8. **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/`](scripts/), the contract for every scenario-roblox-* skill)

```python
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`](scripts/rbx_mcp.py)   | `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`](scripts/rbx_play.py) | `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](scripts/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`](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_check` 0 errors; `Spec` `failed == 0` and `total > 0`, after one red run;
  `Audit` `tally.error == 0` and deprecated APIs gone; `Stats.frames` p95 and worst within budget;
  console free of errors after a playtest; `get_studio_state` back 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, `BindToSimulation` logic 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`](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`](references/procedures.md): the toolkit API in full, chunk rules, and
  each procedure with its tests and results.
- [`references/traps.md`](references/traps.md): current-Roblox traps, MCP and file pipeline traps,
  live corrections.
- [`references/expert-notes.md`](references/expert-notes.md): expert claims by source with
  timestamps.
- [`references/critique.md`](references/critique.md): the rubric for judging an agent's Roblox work
  and a handoff.
- [`references/gui-paths.md`](references/gui-paths.md): Studio menus for the same tasks, and the
  GUI-only steps.
- [`references/sources.md`](references/sources.md): every source with credentials, URLs and best
  timestamps.