bevy_elements
⚠️ Vibe Coded — written by an AI agent working from a human's direction. It is used in a shipping game and covered by tests, but it has had no line-by-line human audit. Read it before you trust it.
Living environments for Bevy 0.20. A sun and moon placed by real ephemerides, the bright-star sky, a seeded climate that schedules hourly weather, a GPU wind field, distance and ground fog, and exposure that keeps night looking like night. Simulation runs in compute shaders; gameplay asks the Environment system parameter and is answered from a throttled async readback.
![]() |
![]() |
![]() |
| Noon, 21 June, 45° N | Sunset 19:30 | 23:00, half moon |
![]() |
![]() |
![]() |
Fog preset |
Clear preset |
with bevy_flora |
Every system was studied in the home-still paper library first. The note for each one, in docs/research/, names the papers, the equations adopted, every deviation from the plan with its source, and the numbers the tests assert.
What is in it
| Plugin | Module | What it does |
|---|---|---|
ClimatePlugin |
climate |
Climate presets, seasons, a seeded hourly weather scheduler, WeatherDirector to force and release weather, SurfaceWeathering (wetness, snow cover) |
CelestialPlugin |
celestial |
WorldClock, GeoLocation, Meeus-accuracy sun and moon (astro), sun and moon DirectionalLights for Bevy's atmosphere, the bright-star sky, a shaded moon disc, exposure |
WindPlugin |
wind |
A camera-centred stable-fluids wind field on the GPU, curl-noise turbulence, travelling gusts, a public WGSL import |
FogPlugin |
fog |
Distance fog from the weather's visibility and a camera-following ground-fog volume written by compute |
ElementsFloraPlugin (feature flora) |
flora |
Drives bevy_flora's wind, gusts, season and wetness |
ElementsPlugins adds the first four. Each is public on its own, so ElementsPlugins.build().disable::<FogPlugin>() works.
Install
[]
= "0.1"
Quick start
use *;
use *;
The API
Frame order
Everything runs in Update in the chained sets ElementsSystems::{Clock, Weather, Celestial, Simulation, Readback}. Systems that write WorldClock, WeatherDirector or settings run .before(ElementsSystems::Clock); systems that read Environment run .after(ElementsSystems::Readback).
The camera
ElementsCamera marks the camera the environment follows. Adding it inserts, where absent: AtmosphereSettings, AtmosphereEnvironmentMapLight, DepthPrepass, Msaa::Off, VolumetricFog, DistanceFog, Exposure, and a zero per-camera AmbientLight (sky light comes from the atmosphere's environment map), and it spawns one Earth Atmosphere if there is none. With auto_exposure (the default) the exposure follows the sun, sky and moon: illuminance-metered like Ghost of Tsushima, mapped with Ward's contrast-based scale factor over Larson et al.'s rod-and-cone thresholds, so a moonlit scene stays dark instead of being exposed up to daylight. ElementsQuality (Low, Medium, High, Ultra) sets every resolution and step count.
Clock, sun, moon and stars
WorldClock holds the local date and hour and advances by Time<Virtual> × time_scale; GeoLocation places the observer. SunState has the sun's and moon's directions, true altitudes, the synodic phase and the lit fraction. The astro module exposes the ephemeris functions (Meeus ch. 7–48: sidereal time, nutation, the sun at 0.01°, the moon from a 51 + 34-term series, phase, refraction, the USNO total-illuminance factor), each tested against the book's worked examples and the SPA reference case. Stars are the 8404 Yale Bright Star Catalogue stars to V = 6.5, precessed to the date, coloured from B−V; the moon is a Lommel–Seeliger-shaded disc from NASA's LROC map. See src/celestial/PROVENANCE.md.
Climate, seasons and weather
Climate (temperate, alpine, desert, tropical, tundra) drives an hourly Markov scheduler over ten WeatherPresets, seeded and frozen by a golden test, with temperature from annual and diurnal harmonics and a −6.5 °C/km lapse rate. Weather is the blended state every domain reads (cover, precipitation, temperature, humidity, wind, gustiness, visibility, storminess, dust). WeatherDirector::force(preset, transition) overrides it and release(transition) hands it back to the scheduler, which kept stepping meanwhile. SeasonState carries meteorological seasons and 0–1 autumn, leaf-fall and blossom curves; SurfaceWeathering the wetness, snow cover and snow depth of exposed ground. Messages: WeatherChanged, SeasonChanged.
Wind
WindState has the prevailing wind, the 10 m speed, the gust factor, the wind at the camera and the simulated field: a rgba16float 3D texture, toroidally addressed, around the camera (64 × 16 × 64 cells of 4 m at High). Shaders sample it with the supported WGSL import:
import bevy_elements::wind::{WindFieldParams, elements_wind};
let v = elements_wind(wind_field, wind_sampler, wind_params, world_position);
bound to WindState::field and WindState::field_params(). WindGust is written when a gust band starts. bevy_elements::noise (gradient noise with analytic derivatives, fBm, Worley, curl) and bevy_elements::grid (bilinear taps into the crate's 2D grids) are also supported imports.
Fog
DistanceFog follows Weather::visibility_m (Koschmieder, 2 % contrast, grey extinction) with a colour lit like the scene; a 240 × 120 × 240 m ground-fog FogVolume around the camera thickens toward the ground and drifts with the wind.
Gameplay queries
use *;
use *;
wind_at answers from the GPU field as of the last readback (4 Hz by default, ElementsReadback) and falls back to the prevailing wind outside it or before the first readback; snapshot_age says how old the answer is.
Plays well with
bevy_flora(featureflora): addElementsFloraPluginnext toFloraPlugin. It writesFloraWind(direction, strength = 10 m speed / 20 m/s, turbulence, gust strength, and everyWindGustas aGustFront),FloraSeason(autumn, leaf fall, blossom, snow =SurfaceWeathering::snow_cover) andFloraWetness. Trees and their sway staybevy_flora's.isomesh/bevy_isomesh: volumetric snow on voxel ground arrives in 0.5.bevy_carnage,bevy_light_grid(Bevy 0.19): no dependency either way. Glue in the game copies plain data across, e.g.SurfaceWeathering::wetnessinto a blood-drying rate, orEnvironment::sun()into a light grid's sky term.
Performance
GPU time per frame on an RTX 3090 (Vulkan), the minimum over Bevy's diagnostic history so it is the full-clock cost (examples/bench_elements.rs; a lightly loaded GPU idles at 210–450 MHz and reads 5–10× slower).
| Quality | Wind grid | Jacobi iterations | Wind | Ground-fog density | Total |
|---|---|---|---|---|---|
| Low | 32 × 8 × 32 | 10 | 0.087 ms | 0.037 ms | 0.124 ms |
| Medium | 48 × 12 × 48 | 10 | 0.119 ms | 0.037 ms | 0.156 ms |
| High | 64 × 16 × 64 | 12 | 0.233 ms | 0.037 ms | 0.270 ms |
| Ultra | 96 × 24 × 96 | 40 | 1.704 ms | 0.037 ms | 1.741 ms |
The budget for wind + fog at High is 0.3 ms. Ultra keeps 40 Jacobi iterations and the plan's grid; it is for large budgets.
Platforms
Native (Vulkan, Metal, DX12) and wasm WebGPU. WebGL2 is unsupported: the simulation needs compute. Simulation state lives in storage buffers and published fields in write-only rgba16float storage textures: the wind kernels share one layout of six storage buffers and one storage texture, the fog kernel one storage texture, inside WebGPU's default limits of 8 and 4 per stage (devices limited to the downlevel 4 storage buffers are not supported). GPU timestamps for the bench exist on Vulkan and DX12 only.
What it deliberately does not do
- Trees, foliage, leaf colour and snow on branches:
bevy_floraowns them. - Isosurface and voxel meshing:
isomeshowns it. - Oceans and FFT waves.
- Bit-deterministic GPU simulation. Astronomy, the weather scheduler and (from 0.2) lightning are deterministic and seeded; GPU results are not.
- A Purkinje shift or other low-light colour change: exposure keeps night dark, but colour handling is left to the tonemapper.
Compatibility
| bevy_elements | bevy |
|---|---|
| 0.1 | 0.20 |
Research
- Bevy 0.20 render APIs — the verified paths and signatures this crate builds on.
- What the home-still corpus holds — the reading list per domain.
- Celestial — Meeus, Reda & Andreas (SPA), Krisciunas & Schaefer, Jensen et al., Ballesteros, USNO Circular 171, Larson et al. (tone reproduction).
- Climate and weather — Richardson 1981, ISO 2533, Jennings et al. 2018.
- Wind — Stam 1999, Harris 2004, Bridson et al. 2007, Kim et al. 2008, WMO.
- Fog — Wronski 2014, Hillaire 2015, Wenzel 2006, Real-Time Rendering.
Examples
Run with cargo run --release --example <name>. Every example takes ELEMENTS_FRAMES=N (exit after N frames) and ELEMENTS_SHOT=/path.png (screenshot on the last frame). Keys: WASD + Q/E to fly (Shift ×5), right mouse to look, [/] time scale, T +1 hour, 1–0 force a preset, R release.
| Example | Shows |
|---|---|
quickstart |
The quick start with a test scene |
day_night |
A solstice day at 45° N in two minutes; ELEMENTS_HOUR=19.5 holds an hour |
weather_tour |
Every preset for 20 s; ELEMENTS_PRESET=Fog holds one |
flora_wind |
bevy_flora trees in this crate's wind, season and rain (--features flora) |
bench_elements |
Per-pass GPU time; ELEMENTS_QUALITY, ELEMENTS_RESOLUTION |
License
MIT OR Apache-2.0, at your option.





