Builtin Library
README.md
profiler
Find where the frame time goes — autonomously, in any scenario, without a human reading a graph.
The engine's raw profiling surface is flat (getProfilingDataTable, getLuauVmDataTable, profiler.stopCapture). These tools compose it into the honest, self-accounting hierarchy a profiler should give you: every node shows its self time (own cost, excluding children), its total, call count, and share of the frame; the frame time no schedule covers shows as an explicit present / idle (vsync) bucket instead of vanishing; and each heavy component's update(dt) is attributed down to the exact component + entity so the cost lands on something you can open and fix.
Call them from the shell (zero profiler <tool> [args]) or over MCP in a tool_exec script (return await tools.use("profiler", "<tool>", ...)).
The tools
summary— the engine's own one-line performance summary, the string that rides a tool response. A different measurement from everything below: this is the published smoothed frame, while the rest report the profiler's effective frame time (wall clock minus the cost of the call doing the asking). Read it when you want the figure other callers are being told; readframewhen you want to know where the time goes.frame— the full attributed tree (self / total / calls / % per node). The deep view; read from the top row down.hotspots— a flat, ranked list of the costliest nodes by self time. The fast "what's slow" triage.scripts— per-componentupdate(dt)attribution: last / avg / max / calls, bycomponent @ entity. The "which of my content is slow" view.record— start / stop / status a background recording that spans a play session. Play across as many turns as you like, then stop to get avg / p50 / p90 / p99 / max frametime and the spike-cluster distribution. The path for "it hitches while I play", which a single live frame can't catch.ring+retro— the latency-immune path.ring onkeeps an always-recording history of the last N seconds;retroreads it back AFTER the fact. Because the data is historical, it does not matter that a tool call lands seconds after the spike — the flaw that makesframe/live sampling unreliable for an agent (which can't control when its calls land).watch+hits— the conditional path.watch <ceilingMs>arms a frame-time watchdog that either records every bad frame (review withhits) or pauses gameplay the instant one hits (freeze + inspect withretro). Runs in the engine's per-frame hook, so it catches stalls an agent poll would miss.heap— where the Luau heap is held.memorysays which component retains bytes;heapwalks the VM's object graph and names what holds them (fields, captured variables, metatables), for the whole heap by type or for one value.gpu— GPU pass timings from timestamp queries, label-aggregated (compute.<shader>,scene.*,post.<effect>,feature.*). The other side of the frame: when the CPU tree shows everything under present / idle, the frame is GPU-bound and this is the view that says which passes the GPU spends it on.
A span whose name ends in wait is time the engine thread spent blocked, not work: render_frame.present.gpu_wait is the frame pacer waiting for the GPU, so a frame whose time sits there is GPU-bound in the same way and gpu is the view to read next. frame and hotspots mark such rows wait and say what each waits for; the core/performance guide explains every row at the end of a rendered frame.
Everything an agent reads is on effective frame time — wall-clock minus the agent's own injected execute cost — so your profiling and authoring calls never masquerade as game hitches. Spike reports are distributions (every spike frame grouped by its dominant hotspot), so one capture shows the whole picture rather than a single anecdotal worst frame. The ring and watchdog are editor-profile only and off until enabled, so a shipped runtime never pays for them.
The autonomous loop
hotspotsorframe— baseline, what's expensive now (best when the scene is steady; live sampling races the ~seconds of tool-call latency).- For anything that hitches:
ring on→ play / test →retro— latency-immune, and the clustered distribution of every spike. Orwatch <ceilingMs>inpausemode to freeze the exact bad frame. scripts— attribute script cost to the component + entity.- Fix, then
retro/recordagain andcompare.
frame, hotspots and flamegraph accept a recording label as their source — including a retro or hits snapshot — so anything you can see live you can also drill into for a recorded window's clusters, worst, or typical frame.