explosion
A turbulent fireball with a rising smoke column, thrown debris and a flash of light. One asset, sized in metres, so the same explosion reads as a hand grenade at 1.5 and as a fuel tanker at 6.
The fireball is a sphere carrying a ridged-fBm field from the shared effects WGSL: the field eats the silhouette away as the blast ages while the colour walks white-hot → core → smoke. Around it, an additive burst of embers reads as the flash itself, an alpha-blended column of smoke rises and spreads for the rest of the duration, and solid debris is thrown outward and falls under gravity. A point light fires with the fireball and decays much faster than the fire that threw it.
local blast = asset.ref("@builtin::systems.effects.combat.explosion", "effect")
blast:play { position = { 0, 2, 0 }, params = { scale = 1.5 } }
Parameters
| Parameter | Type | Default | Range | What it changes |
|---|---|---|---|---|
scale | number | 1.5 | 0.25 – 12.0 | Radius of the fireball at full expansion, in metres. Everything else is proportioned off it: ember speed, smoke size, debris throw, light range. |
coreColor | color | {1.0, 0.72, 0.28} | linear 0..1 | Colour of the hot core. Petrol runs orange, a plasma charge runs blue-white. |
smokeColor | color | {0.14, 0.13, 0.12} | linear 0..1 | Colour the fire cools into and the smoke column carries. |
debrisDensity | number | 1.0 | 0.0 – 4.0 | How much solid debris the blast throws, as a multiple of 26 chunks. At 0 the debris emitter is not created at all. |
duration | number | 2.4 | 0.4 – 12.0 | Seconds from detonation until the last smoke has faded and everything is released. The fireball takes the first third of it. |
light | number | 1.0 | 0.0 – 8.0 | Strength of the light thrown into the scene, as a multiple of the default. Read against the fireball's area, so it holds at both ends of the radius range. |
A parameter outside its range is held at the bound and the holding is reported. A name the effect does not declare raises, and names the ones it does.
Cost
Quoted from the cost block of effect.yaml, which is the declaration
describe().cost returns and a cost suite reads. That block is the source these
numbers are copied from.
| gpuMs | vramBytes | measuredOn |
|---|---|---|
| 0.11 | 699420 | native linux debug build, headless, one unpooled play at the declared defaults held at 0.5 s |
The span is the difference between the frame's GPU span with the blast on screen
and without it, taken as the best of forty samples at each end
(profiler.gpuFrame().frame_span_ms: 5.437 ms idle, 5.546 ms with the blast).
The size is the renderer.gpuMemory() delta across the same play — 327,436
bytes of compute state and 371,984 bytes of emitter geometry.
play here is the unpooled reference path: it allocates its emitters on the
call and frees them when the blast finishes, so the figures above are what one
blast costs on its own.
A finished blast returns its compute state to where it stood, whether it is
stopped by hand or left to expire on its own clock. Its emitter geometry can
stay allocated afterwards — 288,800 bytes, the two sprite emitters' vertex
buffers at 760 bytes per declared slot — so a scene firing blasts in a loop is
worth watching through renderer.gpuMemory().
The three emitters allocate for their declared slot counts — 160 embers, 220
smoke, and 26 debris per unit of debrisDensity — and GPU memory follows the
declared count, not the live one. A wider blast spends its radius on size and
speed rather than on more particles, so the VRAM figure is the same at 1.5
metres and at 12.
What it draws with
| Asset | Role |
|---|---|
shaders/explosion_fireball.shader | the fireball's WGSL, built on fx_noise and fx_shaping |
materials/explosion_fireball.material | that shader, additive, two-sided, writing no depth |
The material is one instance shared by every explosion on screen. What separates one blast from another travels on each fireball entity's own instance-data lanes, not in a material per blast. Neither asset declares a texture or a storage buffer, so the effect adds nothing to a frame's bind budget.
The handle
local h = blast:play { position = { 0, 2, 0 }, params = { scale = 3.5 } }
h.duration -- seconds until it finishes on its own
h.stats() -- { age, duration, radius, fireballRadius, debrisEmitted,
-- emitters, particlesAlive, lightIntensity, finished }
h.stop() -- end it now and release everything
h.isFinished()
Started with held = true, the blast stands still at time zero and h.seek(t)
drives it forward in fixed steps — the same sequence of seeks producing the same
frames every time, which is what a screenshot or a probe needs. Seeks run
forward only: the emitters simulate in steps and have no reverse, so a target
behind the blast's own clock raises. Read an earlier instant by playing again
and seeking to it.
Everything a play spawns is marked temporary, so an explosion that goes off while somebody is authoring cannot end up in a saved scene.
See also
guides { path: "topics/visual-effects" }— finding, playing and parameterising effects.man /zero/source/libs/@builtin/assetTypes/effect.assetType— what an effect asset carries.@builtin::systems.effects.demo.explosion_showcase— the demo scene, which fires all three sizes on a loop with no setup.
Scoped to this part · feeds back into the world's score.