Target: Roblox Studio (October 2026 build) on macOS, driven through the scenario-roblox-expert toolkit.
Expert level here means every client-triggerable action is attacker input: the server owns state, checks each call in a fixed order, and proves it by firing hostile arguments from a playtest client. Detection supplements validation, and visible consequences come late, from varied signals.
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)
- Remotes carry intent, never results. The client names an item, and the server prices it. A weapon reports origin and hit point, never the victim or the damage (Creator Docs, client-server boundary; GnomeCode).
- Check in a fixed order: rate, then arguments, then context. The docs list context, type,
value and rate; running the rate limit first is cheapest [added]. NaN passes
typeof(v) == "number"and fails every comparison, so usemath.isfinite(Creator Docs; Suphi Kaner). Live, NaN and infinity payloads through a handler that only range-checked set an IntValue balance to -9223372036854775808. - An Instance argument is a claim. Check
typeofandIsDescendantOf(expectedFolder): a table can mimic an Instance, and a client can replay a destroyed pickup it still references (Creator Docs; Suphi Kaner). - Every client-triggerable surface is a remote. ProximityPrompt checks distance on
Triggeredonly and ignoresEnabled. ClickDetector has no server checks.Touchedreports the client's physics (Creator Docs; Suphi Kaner). - Movement: Server Authority for new competitive games, a server MoveGuard for existing casual places. The deciding condition is genre plus whether you can adopt fixed simulation, InputActions and streaming (Creator Docs; Jason Knight, Roblox). Server Authority does not stop ESP, aimbots or camera abuse (ev1).
- Validate hits against what the attacker saw. Roblox has no built-in lag compensation (ev1).
Rewind server-recorded positions over
[now - min(ping + 0.1, 0.4), now][added], or use a cone of about 10 degrees (ev1). Obstruction uses static geometry only (Creator Docs). - Consequences climb a ladder and arrive late. Keep a decaying suspicion score, require two distinct reasons before enforcement, and delay visible action randomly by 20 to 90 s [added numbers]. Honeypot remotes and direction honeypots are high-confidence signals (Creator Docs, server-side detection). For movement, rubber-band instead of kicking, because lag causes false positives (GnomeCode).
- UnreliableRemoteEvent only for frequent cosmetic data under 1,000 bytes. The cap is 1,000 bytes (Creator Docs); the 900 figure in a 2025 Roblox Learn video is outdated, and live a 980-byte random buffer arrived while 1,000 was dropped. Both remote types need identical validation.
Establish first
Workspace.AuthorityMode: Automatic means client-owned characters (MoveGuard applies); Server means the engine simulates them.rbx_net.audit_place(place)reads all six settings offline.- Stakes: competitive or casual, live or prototype. A prototype can defer honeypots and the ladder, never validation.
- Remote inventory: name, direction, frequency, payload size, and what server value each handler writes.
- Interactive surface: prompts, ClickDetectors, DragDetectors,
Touchedpickups, and whether their parents are unanchored. - Enforcement policy: kick,
Players:BanAsync, or mitigation only; who handles appeals (Net.reset). - Defaults when unanswered: every remote through
Net.bind, one honeypot,Net.serverOnlyon server-to-client remotes, enforcement as a dry run during tests.
Procedure
Run each stage through the toolkit; full code and test results are in
procedures.md. Status: 24 live tests pass in Studio 0.743 (arena rerun
and Server Authority place on 2026-10-19). Keep NetKit (Guard, RateLimit, Suspicion, Net,
HitCheck, MoveGuard) as ModuleScripts under one server folder. rbx_net.kit_tree(project_dir)
gives the Rojo fragment for a test place; in a user's repository, rbx_net.vendor_kit(repo, dest)
copies the modules in and returns paths inside it.
- Audit. Goal: a list of every remote, handler and interactive instance with findings. How:
rbx_net.audit_place(place)offline (Lune), thenrbx_net.run(studio, "return NetAudit.run(game, Audit)", kit=["NetAudit"], lead_kit=["Audit"])in Edit. GATE: zero errors; every warning fixed or justified; live tally equals offline tally. - Validate and limit. Goal: no handler runs on bad arguments or at flood rates. How:
Net.bind(remote, {args = {Guard...}, limit = {capacity, perSecond}, handler = fn})for every client-to-server remote; idempotent handlers on the server clock. GATE: the audit shows nohandler-unvalidated,nan-unsafe,trusted-valueorno-rate-limitfinding. - Hits. Goal: claims checked against history. How: a 1 s
HitCheck.Historyper target pushed every Heartbeat,HitCheck.meleeandHitCheck.rangedwithExcludeInstancesorIncludeInstancesfor static walls. GATE: out-of-reach, fake-origin, through-wall and offset shots rejected; valid shots hit. - Movement. Goal: speed and teleport hacks neutralized without false positives. How: Server
Authority (Rojo writes the settings, stage P11), or
MoveGuard.startwithMoveGuard.grace(player, s)before every server teleport;enforce = falseonly logs, then measures from where the character stands. GATE: honest walk 0 flags; client teleport and local WalkSpeed changes rubber-banded (live: WalkSpeed 80 for 2.5 s netted 12.6 studs on the server). Under Server Authority, live: W held 2 s moved the server character 32.4 studs, a client teleport 0, local WalkSpeed 80 then W 32.0. - Ownership and surfaces. Goal: no client-owned part decides an outcome. How:
SetNetworkOwner(nil)on unanchored pickups, NPC roots and prompt parents;Net.promptre-checks distance, state and rate; a distance check inMouseClick;Net.touchforTouchedpickups (toucher,CanTouch, distance, MoveGuard reach, shared limiter). GATE: a client CFrame write moves nothing on the server. - Exploit tests from the Client. Goal: proof, not intent. How: in a
Playtest,rbx_net.fuzz(pt, path, gap=...), a same-frame spam, a honeypot fire, thenrbx_net.net_log(pt)in the Server data model. Include a deliberately vulnerable remote as a negative control. GATE: balances and health unchanged,ok_<Remote>equals the valid-case count, no server script error, the negative control pays out. ATouchedpickup has no remote: useexploit_casewith a client teleport onto the part, then aNet.touchpart and a bareTouchedpart side by side (P6). - Detection and consequences. Goal: high-confidence signals, delayed action. How:
Net.init({enforce = ..., enforceDelay = {20, 90}}),Net.honeypot,Net.serverOnly. GATE: one honeypot fire reachesenforcewith a scheduled delay; a flood alone stays belowrestrict. - Latency. Goal: valid play survives real ping. How:
rbx_net.set_latency(studio, 100, 100)in Edit before Play, last-moment swings at a moving target. GATE: rewind hit rate at least 0.8. Live at 202 ms ping: rewind 13 of 13, no rewind 0 of 13 (three runs). Trigger the swings close to reach plus slack: with 0.2 s of margin left, the no-rewind rate swung from 31 to 92 percent between runs. - Bandwidth. Goal: small, on-change traffic. How:
rbx_net.payload_probebefore choosing unreliable delivery; pack withbuffer; sequence numbers on streams. GATE: payloads measured under the cap; stream accepted count equals sent.
Numbers
| Value | Relative to | Source |
|---|---|---|
| 1,000 bytes | UnreliableRemoteEvent payload after compression; a lone buffer fits 996 | Docs; live: random 980 arrived, 1,000 "went over by: 4 bytes" |
| About 500 per second | Client-to-server requests per client, per remote type | RemoteEvent reference; not observed in a Studio playtest (live: 1,000 arrived within 0.5 s) |
| Burst 5, 1 per 2 s | Docs token bucket sample TokenBucket.new(5, 10) |
Docs |
| 50 dropped calls in 1 s | Flood flag threshold, once per window | [added] |
| WalkSpeed + 5 studs/s | MoveGuard allowed XZ speed, dt clamp 0.01 s | Suphi Kaner |
| 6 studs, leak 0.5/s | MoveGuard burst bucket | [added] |
| 0.1 s interp, 0.4 s cap | Rewind window [now - min(ping + 0.1, 0.4), now] |
[added] |
| About 10 degrees | Hit plausibility cone | ev1 |
| 1, 3, 6, 10 | Suspicion levels log, mitigate, restrict, enforce; half-life 120 s | [added] |
| 20 to 90 s | Random delay before visible enforcement | [added] |
| 64, 50 chars | Replicated attributes per predicted instance; name and string length | Docs |
| 0.5 percent | Highest scripted packet loss (5 reads back 0.5) |
live |
| 50 KB/s | Roblox's send and receive target per client (Stewiepfing aims for 25) | Stewiepfing |
Quality gates
Measurable:
- Audit: zero errors; every warning resolved or written down with a reason.
- Fuzz: every hostile case lands in
args_<Remote>orrate_<Remote>; balances, health and pickups unchanged. - Spam: 1,000 same-frame reward fires grant once; purchases never exceed the bucket.
- Hits, movement, ownership, prompts: verdict counters match the test cases (procedures P4 to P6).
- Console during the fuzz: no
ServerScriptService.<script>:<line>error from your handlers. - Latency: hit rate at 100 ms each way within 0.2 of the baseline.
- Types: zero errors on both Luau solvers,
luau-lsp analyzewith and without--flag:LuauSolverV2=true, with a sourcemap built byrojo sourcemap --include-non-scripts(scenario-roblox-tooling gate).
Visual:
- A client capture during play with no flags from
rbx_review.image_checks. - For Server Authority, the visualizer under simulated latency (GUI only, gui-paths.md).
Score the result with critique.md before calling it done.
Common mistakes
| Mistake | Symptom | Fix |
|---|---|---|
Range check without math.isfinite |
Balance becomes NaN or INT64_MIN | Guard.num, Guard.int, Guard.vec3 |
| Client sends price, damage or amount | Free items, infinite money | Client names the item; server looks it up |
| Debounce as the only pickup guard | Duplicated pickups from replays | Guard.inst(folder), destroy on grant |
| Rate limit on the client only | Remote spam still lands | Server bucket per UserId, cleared on PlayerRemoving |
| Kick on one movement flag | Laggy players kicked | Rubber-band, score, two reasons, delay |
| No grace on server teleports | Your own teleport flags players | MoveGuard.grace(player, s) first |
| Unanchored prompt parent | Prompt used from anywhere | SetNetworkOwner(nil) or anchor |
InvokeClient for answers |
Server thread hangs forever | RemoteEvent request and reply with a timeout |
| Batching into an UnreliableRemoteEvent | Silent drops past 1,000 bytes | Reliable for batches; measure with payload_probe |
| Hits checked at "now" only | Valid hits rejected under lag | Rewind window or cone |
| Raycast against moving players for walls | Lag rejects real shots | Static geometry only |
| LocalScript under ReplicatedStorage as loader | Client code never runs | Script with RunContext Client |
Server Authority test driven by MoveTo or character_navigation |
Nothing moves, so every hack looks blocked | Hold keys (Playtest.press); prove an honest walk moves first |
Handoffs
- Receives from scenario-roblox-architecture: the remote list, folder layout and where state lives.
- Receives from scenario-roblox-gameplay: hitbox, projectile and ability rules to validate.
- Delivers to scenario-roblox-data: validated, finite, size-checked values before any save, plus trade and purchase atomicity questions.
- Delivers to scenario-roblox-monetization: grants only from
ProcessReceipt; thepurchase-signalfinding. - Delivers to scenario-roblox-performance: payload sizes, send rates and the Server Authority simulation budget.
- Asks scenario-roblox-luau for buffer packing and Parallel Luau questions, and scenario-roblox-tooling for CI wiring of the offline audit.
Current Roblox notes
- Server Authority reached full release on 2026-07-09.
AuthorityModeServer sets StreamingEnabled, Deferred signals, UseFixedSimulation, NextGenerationReplication and PlayerScriptsUseInputActionSystem. - Scripts cannot read or set those settings from Studio's MCP context ("lacking capability RobloxScript"); write them in the Rojo project or the Properties panel. Check them by behavior: Deferred signals, input-only movement.
- Inside
BindToSimulation, usetime();task.deferleaves the simulation step; effects belong in RenderStepped, not in the simulation. RaycastParams.ExcludeInstancesandIncludeInstancesreplaceFilterDescendantsInstancesandFilterType(2026-04-07);BlacklistandWhitelistare gone.- The Network Simulator is in beta and drives
NetworkSettings; the legacyIncomingReplicationLagstill exists but is not the tool. - Yielding APIs carry the
Asyncsuffix (BanAsync,UnbanAsync,GetBanHistoryAsync).
References
- references/expert-notes.md: principles and judgment by expert, with timestamps and live measurements.
- references/procedures.md: full code for stages P1 to P11 with test ids and live results; loose-part and detector checks (P6), the Server Authority migration list (P11) and ordering traps.
- references/critique.md: pass and fail rubric for your own output.
- references/gui-paths.md: Studio menus for the same tasks, with scripted substitutes.
- references/sources.md: every doc and video, with credentials and timestamps.
- scripts/rbx_net.py: audit, kit tree, fuzz, payload probe, latency and NetLog helpers.
- scripts/agentkit/networking/:
Guard,RateLimit,Suspicion,Net,HitCheck,MoveGuard,Exploit,NetAudit,lune_net_audit.luau.