Releases / v0.100.0

v0.100.0 Keeps Up

2026-09-28 Engine + Hub Latest

Big levels get cheaper: nodes nobody is touching stop costing time every frame, walking characters on mesh levels cost a fraction of what they did, far-away enemies can be put to sleep, and hidden characters stop animating. A game that still can't keep up plays in slow motion instead of falling to a few frames a second, and says why. Games also get a render-resolution setting, every building casts its shadow, and scenes stop picking up paths that only work on your machine. Games on Floptle Cloud can download the replays and levels their players share, and the collections a game stores its data in can live in the project.

Changing a value every frame no longer costs the whole level

Setting a camera's fovY, a light's intensity, node.visible or a post-processing value every frame used to make the engine re-read every node in the scene for the scripts, several times a frame. The cost grew with the level, and the profiler blamed no script. Now only the node that changed is re-read. Writing a value a node already has costs nothing at all.

Adding, removing, renaming, reparenting or retagging a node still rebuilds the scripts' copy of the scene. perf.counts().mirrorRebuilds says how often that happens, and perf.mirrorCause() names the node and the change behind the last one.

A game that can't keep up slows down instead of stalling

When the fixed update costs more than the time it simulates, catching up only makes each frame longer, and a game could settle at 2 frames a second. It now runs one update per frame instead, so it plays in slow motion at a frame rate you can still see and debug. The Console says so once, with the three biggest costs. perf.overloaded() lets a loading screen tell the player their machine is struggling. A networked session keeps catching up, and so does a project with tick_overload: CatchUp in project.ron.

floptle run --timing now breaks each step down by subsystem and by script, counts the steps that ran over, and gives the node count.

Characters on mesh levels cost a fraction of what they did

A walking capsule on a level built from models used to check its collisions against every model whose bounding sphere it was inside, which for large buildings was most of them. It now skips models it can't reach and answers the rest faster. On one level, fifty-three awake enemies went from 16 ms of physics per step to under 4, with every collision exactly as before.

Put far-away enemies to sleep

lua
function fixedUpdate(node, dt)
  if (find("Player").worldPos - node.worldPos):length() > 80 then
    node.vel = vec3()
    node.asleep = true               -- the physics body stops simulating
    script.sleep{ wakeWithin = 70 }  -- the engine stops calling this script
    return
  end
end

A sleeping script isn't called at all. It wakes when the camera comes within wakeWithin metres, after seconds, when something collides with its node, or when another script calls script.wake(node, "enemy"). script.setRate(10) runs a script's fixedUpdate ten times a second instead of every tick.

Every script also got cheaper to run: the engine looks at each script file once per frame instead of once per node.

Hidden and off-screen characters stop animating

An Animation Controller now has a culling setting beside stepped. only when visible skips a model that is hidden or outside the camera's view, and catches it up when it comes back without firing the events it missed. reduced when far also poses every third frame past 30 m. New controllers start at only when visible; existing ones keep animating always. From a script, anim:setCulling(mode) overrides it for one node and anim:setEnabled(false) switches an animator off. perf.counts().animators and .animatorsCulled show the effect.

Ask where a bone is

node:bonePos("Head"), node:boneRot("Head") and node:bones() read an animated model's pose this frame. A ragdoll no longer needs an empty node attached to every bone to find out where they are.

A render-resolution setting for weak GPUs

app.setRenderScale(0.67) renders the 3D scene at two-thirds of the window and upscales it smoothly, while the game's UI stays at full resolution and sharp. On a typical level that took 44% off the GPU time, far more than turning every lighting feature down. render_scale sets the default in project.ron and in Project Settings. It's separate from retro mode, which still decides the resolution itself when it's on.

Every building casts its shadow

Only 16 collidable models could cast the soft distance-field shadow at once, and which ones missed out depended on their file names. Copies of one model now share a single bake, 32 can cast at once, and when there are more, the ones nearest the camera get them. A model with casts shadows off no longer uses the budget. shadow_volumes in project.ron lowers it for a game short on GPU.

Scenes keep paths that work on every machine

Picking a model from the Inspector's list saved the model's full path on your computer, so the scene loaded for you and was missing it everywhere else. Scenes and prefabs now always save paths inside the project as project-relative. A scene that already has full paths is fixed the next time you save it.

floptle check now fails on a full path in any project file, naming the file, line, node and field, and floptle check --fix rewrites the ones inside the project.

Downloading a replay or a shared level works

cloud.get on a blob, such as a ghost replay or a level a player shared, always came back with ok = false and "the reply is not valid JSON", although the file had arrived. It now reads a reply as JSON only when the server says it is JSON. A blob comes back with ok = true and its exact bytes in res.body.

Keep your Cloud collections with the game

Public data, leaderboards and counters on Floptle Cloud have to be declared before a game writes to them. Instead of filling in the game page's form, list them in cloud_collections.ron beside project.ron:

ron
[
    (name: "replays", kind: blobs, access: public, cap_kb: 512),
    (name: "time", kind: rank, keep: min),
    (name: "installs", kind: counter, mode: unique),
]

floptle cloud collections path/to/game compares the file with what fopull.com has and prints the differences without changing anything. It exits 1 when they differ, so a build script can stop on it. Add --apply to make the server match, as the game's owner signed in to the Hub. A leaderboard has to say what it keeps: max for points, min for times, or latest. A collection that is only on the server is listed and left alone, because deleting one deletes its data. That stays a click on the game's page.

Upgrading

Nothing to do. To pick up animation culling on an existing controller, choose only when visible in its header.

Floptle Hub

Linux x86_64 9.0 MB Download → sha256 83424d12616da4961205932ff1fdc8864dd9f9b837abf24a50661cad2d227d30
macOS Apple Silicon 5.9 MB Download → sha256 2e55b519bac1f1d01a70ca52be278a5398a5b4b805f4caa9458d1a02572a1032
macOS Intel 6.4 MB Download → sha256 acbbaeb43e4bc1a57620f8ca1793efaa30f6dc52ae280c621463e4ad5c03660d
Windows x86_64 7.3 MB Download → sha256 461e1c31a81548ec97b2d003e235715d6cddd256d21710d627c2f3a0108dbf84

Engine

Linux x86_64 36.8 MB Download → sha256 d2a7bc03cb1d3820262aa404a939712c9a0d5e62e1ed17adf239daaf2902dfe2
macOS Apple Silicon 28.3 MB Download → sha256 d1c2a9de502065514aa3aea6feb7cac48ffe62ced4c500a4a976a6904ddc111d
macOS Intel 30.6 MB Download → sha256 ca81911b87bb96c0d1e760b329ee0765b300b9da69a64c6c541f279395b0c780
web 4.5 MB Download → sha256 e74581b066672497fc552ca62e82510dc7d2aa6c49eabcd15e83d31f40362836
Windows x86_64 34.5 MB Download → sha256 a31aa360d7977d61c9d817f3fd2df753ad6bb7047de118689ec8121bc1822cb5

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.