Releases / v0.89.0
v0.89.0 Stay In Your Lane
2026-09-11
A script stays inside its project, a loop without an exit stops the script rather than the editor, a game reaches public addresses only, the editor no longer goes down with the terminal it was launched from, and a relay keeps its shape under load. Nothing here changes how you write a game; it draws lines a game could previously cross.
A path is relative to the project, and stays inside it
Every path a script or a scene file names — assets.getFile,
assets.getContents, node.model =, a texture, an audio clip, a scene, a script
name on a node, a terrain save folder — is read relative to the project folder
and resolves inside it. One that would land anywhere else, whether spelled as an
absolute path or climbed to through .., resolves to nothing, and the Console
says which rule refused it, once per distinct reference.
A game runs on a machine that is not yours. What is on that machine's disk is not your game's business, and a scene file is data that may have been written by somebody else.
assets.getContents lists at most 20 000 files and says so when it stops, and it
does not follow a linked folder. A linked package's folder still resolves,
because that is where a linked package is.
A loop without an exit stops the script, not the editor
while true do end in update used to freeze the editor, the player and a
dedicated server alike, forever, with nothing to say why.
A pass into your scripts may now run for 2 seconds — 500 ms on a dedicated
server, adjustable with floptle serve --script-budget-ms. Past that, the script
that overran is stopped with an error naming the budget, and it is not called
again until you edit its file. Everything else keeps running.
The scripts share 512 MB between them. A table that only grows hits "not
enough memory" in its own call, and the rest of the game goes on. A print
inside a loop keeps a thousand lines a frame on the Console and reports the rest
as a count, and a save slot holds at most 10 000 keys and 4 MB.
A game reaches public addresses only
http.* from a script connects to public addresses and nothing else. Loopback,
private networks, mDNS names, .internal names and link-local addresses are
refused, judged on what a name resolves to rather than on how it was spelled, and
the refusal is res.error with the rule named.
The editor's Play is the one exception: hitting http://localhost:3000 while
developing is the ordinary case and keeps working, and the Console says once per
session that an exported game and a dedicated server will not allow it.
Redirects are no longer followed. A 3xx arrives as an ordinary reply with
res.location, and the script decides. A request body is capped at 8 MB like a
reply, and Host, Content-Length, Transfer-Encoding and Connection are the
transport's headers to set.
A script acts as the player, not as the developer
account.* from a game reaches the player's surface — /wallet, /missions,
/games/… and /me/… — and nothing else. The token behind those calls belongs
to whoever is signed in to the Hub on that machine, and the developer endpoints
under /cloud/… (keys, builds, usage) are not a game's to call on a player's
behalf. A call to one is refused at the call site with the rule named.
A project's packages ask before they are trusted
A project that arrives with packages inside it — a folder or a zip whose
packages/ carry manifests asking for Files or Network — loads them with
no permissions the first time it opens, and a banner across the top of the
editor names each package and what it asks for. Trust this project grants
them and remembers that set of packages for that project; Keep them restricted
leaves them loaded with nothing for the session. A changed manifest asks again.
A withheld API is still there: http.get in an untrusted package raises with
the trust rule named, not as a package that forgot its manifest. floptle exec
is not held to any of this — a script you named on your own command line is
your own code.
The editor survives losing its terminal
Every line the engine writes to the terminal now survives the terminal going
away — a closed window, a pipe nobody reads. The Hub launches the editor with its
output in a log file of its own (logs/ under the Hub's data folder, the last
five kept), which is also the file to send along with a bug report.
openUrl parses what it opens
openUrl, ed.openUrl and sys.openUrl parse the address before anything
opens it: http or https, a host, no username or password, and nothing in it a
shell could read as its own syntax. An address that fails is refused at the call
with the reason, on every platform. On Windows the link is handed to the shell's
own open call rather than through cmd.
A relay keeps its shape under load
Every relay, the one you self-host included, now refuses a few shapes: one
lobby per connection, at most 4 096 lobbies and 64 clients in each, ten lobby
opens and thirty joins a minute from one address, half a megabyte a second from
one connection, and a lobby nobody has joined for half an hour ends unless its
host is a dedicated server. A reliable message a client pushes at a host is
capped at 64 KB, a host's at 128 KB — four times the largest prefab any shipped
game spawns. A refusal is a sentence on the wire, and each is counted in the
relay's report. floptle-relay --no-address-limits lifts the two per-address
rates for a load test run from one machine.
A host checks what a client sends on receipt, not only at the sender's queue: an RPC value past the wire's guardrails is dropped, a server-only message from a client is ignored, and both are counted.
A managed relay is verified by name. A relay reached under fopull.com
has to present a certificate for that name; a self-hosted relay or a direct
host:port keeps the dev-trust model, where the lobby code is the secret. Until
the managed relay's certificate is live, a connection that cannot verify it goes
ahead on the older model and says so once in the Console. A later release will
refuse instead.
A managed relay that cannot refresh its key list backs off — one second, doubling to its normal half-minute — and says so once when it starts, at most once a minute after, and once on recovery with the window in it. It kept serving through a nine-hour outage exactly as designed; it also retried fifteen times a second and wrote every attempt to a journal that then lost everything before the storm. That rate is gone.
docs/multiplayer.md now says the honest thing about a game key: a stranger
with it can use up your plan's players, it cannot cost you money, and rotating
it is what rotation is for.
For a server you run
A dedicated server's unit keeps its runtime and state directories to its own user, denies the process link-local addresses, and gives up a handful of capabilities a game server never uses. Its status file names the prefix of the game key rather than the key. A deployment row carrying a control character in any field is refused with the field named, rather than written into a unit file.
One VM
The vm-luajit escape hatch — buildable for one release after Luau became the
default, as ADR-0028 scheduled — is removed. Luau is the only VM the engine
embeds, native and browser alike. scripts/vm.sh still switches the code
generator (luau-codegen) for benchmarking.
Upgrading
If a script of yours reads a file by absolute path or through .., it now reads
nothing and the Console says so — move the file into the project and reference it
relatively. If a script reaches localhost from an exported build, it is refused;
in the editor's Play it still works. If http.get was relying on a redirect being
followed, read res.location and make the second request yourself. If a game
calls account.get("/cloud/…"), it is refused — that is a developer call, and
the portal is where it belongs. If you open a project that carries packages
asking for permissions, you will be asked once. If you run a relay and a load
test from one machine, pass --no-address-limits to the relay. Nothing else
needs a change.
Floptle Hub
Engine
The binaries are not signed yet. On macOS, right-click the app and pick Open the first time. On Windows, if SmartScreen appears, pick More info then Run anyway.