Releases / v0.82.0

v0.82.0 Way Out

2026-08-29 Engine Latest

Your main menu's Quit button works. So does a Video tab: frame pacing and internal resolution are things a script can read and change now, which they were not.

app.quit(), and the rest of a settings screen

Every game has a menu with Quit on it, and until now there was no way to wire it — the button was either drawn dead or not drawn at all. There is a new app table:

app.quit() end the game
app.title() / app.version() for the top of a menu, and an about box
app.vsync() / app.setVsync(mode) "On", "Adaptive" or "Off"
app.retro() / app.setRetro(on) the retro presentation
app.retroHeight() / app.setRetroHeight(px) the height it composites at
app.retroIntegerScale() / app.setRetroIntegerScale(on) whole-number upscale, letterboxed

What quit does depends on where the game is running, and they are honestly different things. An exported build closes, with your save.* data flushed first — somebody quitting from a settings menu expects the setting they just changed to have been kept. The editor stops Play instead and says so, because an editor that closed because a game under test called quit would take your unsaved work with it.

The rest of an options menu was already here and is documented together for the first time: audio through audio.track(name):setVolume(db), accessibility through access.*, the picture through the Post Processing node's own component, and rebindable controls through input.startRebind.

lua
function start(node)
  app.setVsync(save.get("vsync") or "On")
  app.setRetroHeight(save.get("pixelHeight") or app.retroHeight())
end

A setting you change this way lasts for the session. These live in project.ron, which is the file that ships to everybody who plays, so pressing Stop puts it back — persist the player's choice yourself with save.*, the same way access.* works. And a mode setVsync does not recognise is an error rather than a shrug: a control that silently keeps the old value is a control that appears to work.

Not here yet: window resolution, fullscreen and monitor choice. Those are one question and it deserves answering properly. In a pixel-art game app.setRetroHeight is the "resolution" a player means, and it was already a live setting.

A broken project.ron says so

A project.ron the engine could not parse was quietly replaced with defaults. One misplaced bracket and the project opened with no title, the wrong frame pacing, none of its layers and none of its mixer — looking exactly like a project nobody had configured, with nothing anywhere saying the file had even been read.

It now names the file, the line and column, and says the project has opened with default settings and that nothing has overwritten it. A project with no project.ron at all stays quiet; that is a real state, not a fault.

The profiler stops blaming the wrong script

perf.scripts() times each script with a wall clock, so anything that happened while your script was on the CPU — a garbage collection, the operating system taking the core away — was reported as that script's cost. Scripts with no update at all were being listed with 12–21 ms peaks, which sends you to optimise a file that did nothing.

A script with no hook for a pass is no longer listed. A stall can still land on one that is running — that is what sampling a wall clock means — so docs/scripting.md now says which number to act on and what makes a worstMs spike.

While looking at that: if the collector is what you are chasing, the biggest source of it in most games is vectors. Every vec3 a script builds is an object, so a + b, v:normalized() and node.pos each allocate, while scalar reads like node.x and v:length() allocate nothing at all. A hot loop written in components rather than vectors produces no garbage, and that is usually the largest win available to a game script today.

Upgrading

Nothing to do.

Floptle Hub

Linux x86_64 7.5 MB Download → sha256 c92dc90aabde22cc77be887479777a6c7d0388647dcfa40b248cbb40636e8441
macOS Apple Silicon 5.7 MB Download → sha256 fc29d7dcc589f7a2d85bc50cfd92a9090609982d781f9deeec7d9330f1e373d9
macOS Intel 6.2 MB Download → sha256 2e0f1312558acb2eee87cafc971e36c8587edbc2ee6f11fc2bdd2bffcbd74bcd
Windows x86_64 7.1 MB Download → sha256 e0cb6c89bda7692f46d8700a274f4cab1c774bab2e30cd3bee2cab3858570ccc

Engine

Linux x86_64 20.9 MB Download → sha256 13cb0a7e9e18d6295e7d0be23ff7ebaa363d083158fa4ac7119e3decbed734e1
macOS Apple Silicon 16.9 MB Download → sha256 e1116016bf7319b14ad18616fdc6dd686c3dddad9aeae7eed19191b7759e55c7
macOS Intel 18.2 MB Download → sha256 22192e63dab92d8cee0e6d0271c9a6492e4517b9d9e71acc49f53e964c549bb5
Windows x86_64 20.6 MB Download → sha256 622b8160d9ff2f605c611a13e636fa66489893e9457acf260b04be321a55eb61

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.