Releases / v0.102.0
v0.102.0 Leaves A Mark
2026-09-29 Engine
Sun shadows can come from a shadow map, which in a dense city cuts the lighting cost by more than half and lets every drawn mesh cast. Blood, scorch marks and bullet holes wrap round edges and climb walls without being nodes. Characters can reach for things, look at things and plant their feet. A server can load levels as layers that every player follows. Voice chat can sit in the room with reverb sends and muffling, and quiet microphones are levelled. Terrain flattens are dozens of times cheaper, and script lines can hide behind the scene.
Sun shadows from a shadow map
Turn on shadow map in the Lighting panel (shadow_map: true in a scene's
lighting, or env.shadowMap = true), and the sun's shadows come from a shadow
map instead of a march through the distance field. In a dense city at 1080p,
lighting fell from 9.5 ms to 4.1 ms, plus 0.65 ms for the map, and the whole
GPU frame from 12.9 ms to 8.0 ms. The cost stays flat as a level adds
buildings and props.
It also changes what casts. The march only saw colliders, so a mesh that
doesn't collide cast nothing, and neither did geometry built with the map
tools. With the shadow map, everything that is drawn casts. The shadow
softness widens the edge, and the shadow distance sets how far around
the camera it reaches. It's off by default, so no game's look changes on update.
perf.gpu() shows it as its own "sun shadow map" pass.
Levels in one session
A server's scene.load("levels/cave", { additive = true }) now reaches every
player, including players who join later. The level's networked nodes
replicate, and offset = vec3(...) places it anywhere, so a hub with portals
into separate levels no longer has to be one giant scene. Each level brings
the geometry built for it with the map tools. scene.unload only lets go of
that level's sounds, effects, animators and colliders, so it no longer
hitches the rest of the world.
Switching a node off with node.enabled = false now takes its colliders out
of physics and rays too, and they come back when it's switched on. A spawned
prefab's walls and floors collide the moment they appear.
Voice that sounds like it's in the room
A voice, or any sound, can now go partly into a reverb track and partly into
another: sends = { ["Verb Large"] = 0.4 } on audio.play and
voice.attach, or :setSend(track, level) on either handle. It's one stream
heard dry and wet together, so moving between rooms glides. lowpass = 850,
or :setLowpass, muffles a voice through a wall.
Each machine now levels its own microphone to a steady speaking level before
sending it: quiet microphones are raised and loud ones lowered, and silence is
never pumped up. voice.setAutoGain(false) turns it off, and
voice.setInputGain(db) is there for a settings slider. A microphone plugged
into one side of a stereo interface is no longer halved. A mistyped mode or
falloff on a voice is now reported, as it is on audio.play.
Marks that wrap round corners
decals.add lays a picture on the world's surfaces: level meshes, terrain,
and Collidable boxes and planes. Near an edge it folds over and carries on
down the side. On a floor by a wall it climbs the wall and never pokes
through. On a rock it follows the rock. It's lit, shadowed and fogged like
the surface it lies on.
decals.add{ texture = "textures/decals/blood.png",
pos = vec3(hit.x, hit.y, hit.z), normal = vec3(hit.nx, hit.ny, hit.nz),
size = 0.8, rotation = math.random() * math.pi * 2 }
A decal isn't a node. Every decal that shares a picture is one draw, so a
fight can leave hundreds. The engine keeps the budget: 1024 until
decals.setMax says otherwise, dropping the oldest. decals.set fades or
tints one, decals.remove and decals.clear take them off, and
perf.counts().decals says how many there are.
Characters reach, look and plant their feet
A script can now change a rigged model's pose after its animations have posed it, one frame at a time:
anim:setBoneRotandanim:addBoneRotturn one bone, andanim:setBonePosandanim:addBonePosmove one. For example, lean the chest over a run, or drop the hips on a slope.anim:reach("Hand", target, { pole = elbow })bends an arm or leg so the hand or foot lands on a point: a grapple line, a wall, a step.anim:lookAt({ "Spine2", "Neck", "Head" }, target, { limit = 80 })turns a head, or a whole neck, toward something, within a limit.anim:boneWorld("Hand")says where a bone ended up. It now also works underfloptle runand on a dedicated server, so a hitbox can follow a hand where nothing is drawn.
The animation guide has a foot-planting recipe for uneven ground.
Controllers can do more too:
- A blend state mixes clips by a number a script sets with
anim:setParam, such as aim left, centre and right byaimX. - An additive layer adds its motion on top of the layers below.
- A mask limits a layer to the bones you name.
mirror: trueplays a clip as its left-right mirror image, so one left wallrun serves as the right one too.anim:setSpeed(x, "Movement")speeds up one layer and leaves the others alone.
A map view leaves the terrain alone
terrain.lodAnchor(node) makes terrain detail, far planets and far craft
follow that node instead of the camera. Pulling a map camera out into space
no longer throws away the ground under the player, and closing the map no
longer rebuilds it. Scattered props keep nothing loaded while the camera is
far above them.
A lower render scale stays sharp
A render scale below 1 now sharpens as it scales up. app.setRenderSharpness
sets how much, from 0 to 1 (the default is 0.4). draw.nativeLines(true) draws
script lines over the finished picture at full resolution: one pixel wide and
exactly the colour asked for, where before they came out soft and shimmering.
A host that blips keeps its lobby
When a listen host's connection dropped for a moment, its lobby was held for it, but only a host on a managed relay could claim it back. Every relay now gives the host a private token along with its lobby code, and a host that reconnects gets its lobby, code and players back with it. The relay needs this version too.
Cloud data by name
cloud.rank("laps"), cloud.docs("stages"), cloud.blobs("replays") and
cloud.counter("installs") read and write the Cloud's data collections
without building paths by hand. Every call reports errors the same way:
{ code, message, status }. floptle check warns about a collection a script
uses that the project doesn't declare.
Cheaper spawning, rays and terrain edits
- Spawning or destroying a node no longer costs time for the whole scene. A game that still makes the scripts re-read the whole scene every frame gets one Console warning naming the cause.
raycastchecks only what the ray passes near, and hits level meshes exactly. On a level of a hundred models, a ray costs a fraction of what it did.raycastManycasts a batch in one call, andperf.counts()reports the rays cast and their time.terrain.sculpt(..., "flatten")levels to a planet's up where it stands, or to a plane you name with{ normal = ... }. Flattens and smooths now cost the ground they move rather than everything around it: fifteen building pads in one frame on a planet went from nearly 8 seconds to a fifth of a second. Edits beyond 64 a frame wait their turn instead of being dropped.perf.hitches()lists every slow frame since you last asked, with what took the time, so a probe that checks every few seconds still sees each spike.
Shadows you can budget
The 32 shadow casters nearest the camera are the ones that cast, and
node.castShadow = false takes one out. env.shadowSteps trades
shadow-edge precision for speed on a weaker GPU. perf.gpu() shows how long
each pass took on the graphics card, so you can see where frame time goes.
Also
-
draw.depthTest(true)hides the lines drawn after it behind whatever is in front of them, drawn native or not: a ground reticle behind the character, an orbit behind its planet. -
draw.polylinedraws a line through a list of points, anddraw.conicdraws an orbit's ellipse or hyperbola. -
A script's variables survive its node being switched off and on.
-
An export skips anything listed in
.floptleignore, lists the heaviest files nothing in the project names, and no longer warns wrongly about files outside the project. -
floptle shot --frameslooks through the scene's own camera unless--turnis given.
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.