Playtest control
playtestControl live game-testing sessions in Roblox Studio: start, stop, add players, wait for conditions, install controllers, hotpatch scripts, and push changes without losing runtime state.
Instructions
Control the live playtest and the in-engine runtime. Keep ONE playtest alive; a restart costs ~3 s and loses play-DM state.
start {mode: play|run|multiplayer, players}: waits for the runtimes → {running, mode, players, peers, started_ms}. multiplayer spawns a server DM plus
players(1-8, default 2) client Studios (client:1..N; slow → job handle). no_peer: turn on "Load User Plugins In Run Modes" / install the plugin (test still running: Stop in Studio).stop → {stopped_ms}. status → {running, mode, players, peers, elapsed_s, controllers}. add_players {count} (multiplayer).
run_until {dm, predicate | predicate_file, timeout_ms ≤ 120000, interval_ms, args}: Luau predicate evaluated in-engine each Heartbeat until truthy → {result: true|'timeout', value, elapsed_ms, checks}.
install {dm, name, code | code_file, persist}: code returns { load = function(ctx) … end, unload = function() … end }. ctx: S, assert(name,cond,detail), milestone, emit, log, onHeartbeat, onEvent, every, after, player, character(), input.*, moveTo (straight line), pathTo (pathfinding), state(), storage. Same name replaces; asserts/milestones → /events. persist: kept by the bridge, re-installed whenever that DM appears in a later playtest.
uninstall {dm, name} (drops the persisted entry). list → controllers on all peers + persisted.
hotpatch {dm, path, source | source_file, restart}: writes a script Source in the live DM and restarts it, playtest kept (ModuleScript: re-require needed).
push {paths, dm, parent, replace}: copies edit-DM instances into the live DM at their own paths (or under parent) → {dm, paths, count, bytes, replaced, skipped?, replicated}; replace (default true) removes a same-name, same-class sibling first (never Terrain, the camera, a character); ephemeral, not undoable. dm: 'server' | 'client' | 'client:N'. *_file = absolute path the bridge reads (never heredoc Luau). Slow actions return {job_id, status:'running'} after wait_ms; use
job.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| dm | No | Target DataModel: 'edit' (default) | 'server' | 'client' (lowest-numbered) | 'client:N' | |
| args | No | Available to the program as ARGS | |
| code | No | install: Luau returning { load = function(ctx) … end, unload = function() … end } | |
| mode | No | start: default play | |
| name | No | install/uninstall: controller name | |
| path | No | hotpatch: script path, e.g. ServerScriptService.Main | |
| count | No | add_players: clients to add to the running multiplayer test, 1-8 (default 1) | |
| paths | No | push: edit-DM instance paths to serialize into the live DM | |
| action | Yes | ||
| parent | No | push: parent path in the target DM (default: each instance lands at its own edit-DM path) | |
| source | No | hotpatch: full new source | |
| persist | No | install: keep it in the bridge and re-install whenever that DM appears in a later playtest | |
| players | No | start (multiplayer): client Studio processes to spawn, 1-8 (default 2) | |
| replace | No | push: destroy a same-named sibling at the target parent first (default true); false keeps both | |
| restart | No | hotpatch: toggle Disabled to restart the script (default true) | |
| session | No | Studio session GUID or unique prefix (default: the active hub; required for writes when several Studios are connected) | |
| wait_ms | No | Wait this long for completion before returning a {job_id,status:"running"} handle (default 25000) | |
| code_file | No | install: instead of code: absolute path of a file holding the Luau (read by the bridge; UTF-8, BOM ok, ≤ 4 MB). No shell/JSON escaping touches it | |
| predicate | No | run_until: Luau expression or chunk; truthy ends the wait | |
| timeout_ms | No | run_until/push: default 30000, max 120000 | |
| interval_ms | No | run_until: 0 = every Heartbeat (default) | |
| source_file | No | hotpatch: instead of source: absolute path of a file holding the Luau (read by the bridge; UTF-8, BOM ok, ≤ 4 MB). No shell/JSON escaping touches it | |
| predicate_file | No | run_until: instead of predicate: absolute path of a file holding the Luau (read by the bridge; UTF-8, BOM ok, ≤ 4 MB). No shell/JSON escaping touches it |