Releases / v0.85.0

v0.85.0 Web World

2026-09-08 Latest

Your game runs in a browser. It has voice chat, a dedicated server you can put on a box and leave, and every export now ships a real player instead of the editor wearing a disguise.

This is the stable release of everything the 0.85.0 betas carried, plus the last round of fixes found by going looking for them.

Your game, in a tab

File ⏵ Export Game… ⏵ Web (browser) stamps a folder you can upload anywhere that serves static files — itch.io, a bucket, your own site. It is the same project, the same scripts and the same renderer; there is no separate "web version" of your game to maintain.

Everything a game does at runtime works there: saves (they live in the browser and survive a reload), audio, input, pointer lock, and multiplayer through the relay. What a page cannot do, it says so about rather than failing quietly.

Sound now works in a browser. It did not in the betas: the feature flag that keeps a Linux server from needing a sound card was also switching audio off in the tab. A browser build with no sound at all is now a browser build with sound.

Clicking Play no longer shows a black screen while the engine boots. The loading panel stays up until there is something to look at, and if the engine cannot start — an older browser, a machine without the GPU flag — the panel says so instead of leaving you with a black rectangle.

A web build fills its window. It used to letterbox itself into a black border at whatever size the page gave it, with no way out. There is a fullscreen control now, and the canvas follows the window.

A server you can leave running

A dedicated server is a server bundle: your project, with everything nobody on a server can see or hear left out, and a manifest naming the scene to host and the engine version to host it with.

floptle export . builds/server server --scene scenes/lobby.ron

There is no binary in it. The machine runs its own copy of the engine version your bundle pins, so you never cross-compile for somebody else's processor, and a bundle of a 45 MB game is 25 MB.

It refuses, with the reason, at your machine rather than as a deployment that fails on a box you cannot see: a project with no networked nodes, a rollback scene (those are simulated by every player, so a server has nothing to drive), or a scene that is not in the bundle.

A dedicated server is no longer a player in its own lobby. This one was worth the release on its own. A server answers net.isServer() exactly as a hosting player does — correctly, because the simulation does not care who started it — so a lobby script would seat the empty machine as a player, deal it a character, count it toward "enough players to start", and then wait forever for it to press Ready. A match on a dedicated server could never begin, and nothing said why.

lua
if net.isDedicated() then return end   -- nobody is sitting at this one

net.isDedicated() is the one thing a game cannot work out for itself. Both kinds of host are the server; only one of them has a person at it.

Voice chat

Players in a session can talk. A remote speaker is an ordinary sound in your world, so it is positioned in space and goes through your mixer like anything else — a voice across the room sounds like it is across the room.

lua
voice.setForward(true)      -- open the microphone

Nothing is required of you beyond turning it on. If a speaker's audio drops for a moment, they are heard again when it comes back rather than going silent for the rest of the session.

Multiplayer you can test without a second person

floptle run . --seconds 10 --ghosts 2

joins that many headless clients to the session your project hosts, and reports how many replicated nodes each one was actually sent — which is the number that tells you whether hiding a node from a player worked. floptle run --join connects to a server that is already running, and now prints why it could not when it could not, instead of pointing at an empty log.

Two knobs a tint could not reach

A tint multiplies, and a multiply can only take light away. Give a character a team colour on a mid-toned, ambient-lit model and "crimson" arrives as a slightly warm grey — the one thing the tint exists to say, which player is this, is the thing it says worst. The way out was always a Material, which says it perfectly and costs the model every texture it was imported with.

So a tint carries the two knobs that add light:

lua
model:setTint{ rim = teamColor, rimStrength = 1.3 }  -- an additive edge
model:setTint{ ambient = 1.6 }                       -- lift it out of the room's shadow

The rim reads on a dark costume and a bright one, from across a room, which is what actually tells two team-coloured fighters apart in motion. Both are on the ◐ tint rows in the Inspector too, and an animation clip can key either.

Fields you leave out keep their value, so a hit flash that rewrites the colour every frame does not cost the model its rim. setTint(color) positionally still means exactly what it always did.

Fixes

A tint set only by alpha turned models black. setTint{ alpha = 0.5 } — the spelling the documentation gives — was read as a colour rather than as options, and a colour with no red, green or blue in it is black. It fades, now. Anything in that table that is not a colour or a number says so by name instead of silently doing nothing.

Hosting on Floptle Cloud could refuse for a day after a region opened. The list of regions is cached so that going online does not wait on a web request — and a cached list with nothing open in it was kept just as long as a useful one. A region that went live was invisible until the cache expired, and relaunching did not help. A list with no open region in it is no longer worth keeping.

"Your plan" is no longer guessed at. When the account service cannot be reached, the Hub said you were on the free tier, which is indistinguishable from actually being on it. It now says it could not check, shows the free tier's limits, and re-checks next time. Nothing is blocked either way, and a script that reads a player's tier gets "unknown" rather than a wrong answer.

A second net.host or net.join is refused out loud. It was always refused — a second session must not tear down the first — but it was refused silently, so a script that hosted twice got a result it could not tell from success. floptle run --join quic://… also takes the address in the spelling its own help prints, which previously produced a lookup failure for a hostname you never typed.

A creature could walk off a sofa into the room below. Baking a navigation mesh treated the eroded gap beside furniture and doorframes as a ledge, found the floor of the storey underneath, and linked a drop straight through the ceiling — 3,170 of them in one two-storey house. A drop now has to have somewhere to fall through. Real ledges and balconies are unaffected.

A light's Inspector says how many of the sixteen are in use. Sixteen lights reach the shader at once; past that the ones contributing most win and the rest are simply not drawn, which reads as "my seventeenth torch does nothing". The count is on every light now, and it warns when something was cut.

Two dozen messages had holes in them — hover text and errors with a run of blank space in the middle of a sentence. All closed.

Documentation

The documentation site now carries only pages that help you use the engine and are true today. Three that were neither — a north star, a superseded design record, and a work plan — have come off it, and the particle system's page, which described an asset format the engine does not have, is rewritten against the one it does.

Upgrading

Nothing to do. Scenes, projects and scripts from 0.84 open unchanged, and a scene that only ever set a tint colour writes the same bytes it always did.

If you use Floptle Cloud, docs/fleet-agent.md is new and is only for people running a region's box; nothing about hosting a game changed.

Floptle Hub

Linux x86_64 7.5 MB Download → sha256 1c102ffa0504f63ef41d134093d85e81188324fefed7f1da3384067ee6c44e28
macOS Apple Silicon 5.7 MB Download → sha256 f81e3ee997bf3cbf411279622bb7ddde6029ac8f536a2a0e6b125f78d31b7fab
macOS Intel 6.2 MB Download → sha256 deda666b98138b7a3bf2df4e200a6b9db535316943bdac94c1828f6a0ce0618b
Windows x86_64 7.1 MB Download → sha256 510006dbc45b966307cd40dfbb895d079add01b73ba6ee69d65de50c499299fd

Engine

Linux x86_64 33.4 MB Download → sha256 8b84ba751df3fe63f0f6e1fba0007835c414048b5c3588df89fc2340fd2ddf26
macOS Apple Silicon 27.1 MB Download → sha256 15115233694e982097184e85e79c4c01a1f50ef29850a9ff92fa601638122958
macOS Intel 29.3 MB Download → sha256 2d6458e9670a63f778233e753e89787b15f081cd3363366776a2cdac1b04bdd3
web 4.2 MB Download → sha256 50ff21dc50fddc028a392cead99b126053559f3f9d2050219f86b0efc3ea1b10
Windows x86_64 33.0 MB Download → sha256 3c4c89fdbd1ddecccbd988c539eba53badcc5e3793f7d9cb93533becdcef96c3

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.