




ui_gallery, default themeWhy
Every Bevy game begins by rebuilding the same shell: a main menu, a settings screen that saves, Esc to pause, volume sliders, quality presets, a way to test it. Hull ships that shell, so the first commit is gameplay.
The crate is called engine today. Hull is a working name.
Inside the shell
Shell
- App bootstrapWindow or headless, CLI flags, data and asset folders.One call
engine::app(GameInfo::new(id, title)) - App statesBoot, MainMenu, InGame. Games hang their own sub-states under InGame.Built in
- SettingsCore settings plus a typed section for your game. Saved as JSON with atomic writes; a corrupt file is set aside, not fatal.Persisted
- Settings screenEach FieldDef becomes a row: toggle, slider, choice, cards or swatches. Reset tab restores defaults.Generated
- Input and cursorBindings, mouse look, cursor capture that lets go on menus.Built in
- AudioSoundLibrary with variants, volume categories, muting for test runs.Built in
- Graphics qualityLow, Medium, High and Ultra presets for AA, ambient occlusion, bloom and shadows.4 presets
- SavesDataDir JSON helpers with the same atomic writes as settings.Built in
UI kit
- ThemeRe-brand the whole kit with one accent colour. Barlow and Barlow Condensed are embedded.with_accent
Theme::default().with_accent(Color::srgb(1.0, 0.78, 0.22)) - WidgetsToggle, slider, choice, cards and swatches. Keyboard and mouse.5 kinds
- Screen stackEsc pauses, Back pops. The game pauses when the window loses focus.Built in
- Main and pause menusItems, a side panel and a pause header, all from one MenuConfig.MenuConfig
- Toasts and FPS counterPost a toast from any system, even in headless runs.Built in
Play
- FPS controllerKinematic collide-and-slide, mouse look, crouch, sprint, jump.feature fps
- Chase cameraThird-person follow for any entity. Looks ahead, orbits, pulls in front of walls.ChaseCamera
- MeshKitLow-poly vertex-coloured meshes from Bevy primitives. One mesh per object type.MeshKit
- Physicsavian3d 0.7, paused with the game. Re-exported as engine::avian3d.feature physics
- Sun and shadowsSunSetup: sun angle, shadow distance and cascades for arenas or open maps.SunSetup
Render
- ClassificationAnything that moves, has physics or animates is an actor at full detail. The rest becomes scenery.Automatic
- Distance detailSmall props lose shadows, then fade. Big pieces cross-fade to a flat far copy.On
- StreamingScenery cells spawn around the camera with hysteresis. Colliders are never streamed.SceneryStream
- Frame pacingMenus and pause redraw at 30 Hz, 8 Hz unfocused, so the GPU idles.On
- Idle skipA frame where nothing visible changed is simulated but not drawn.Opt-in
Devtools
- Control port and MCPTests and AI agents drive the game: keys, mouse, state, screenshots.Dev only
- Offscreen screenshotsA hidden window renders to an image, so captures work behind a fullscreen app.Dev only
- Perf overlay and session logCPU, GPU, memory and frame times per span, plus a log of state changes.Dev only
- Metrics windowF4 opens a second window with live charts and run history tied to git commits.F4
- Ship checkpackage.sh refuses to package if any of the ashlar debug crates behind the devtools is still in the tree.Enforced
use engine::prelude::*;
fn main() {
engine::app(
GameInfo::new("orbs", "Orbs").tagline("Collect them all."),
)
.add_plugins(MyGamePlugin)
.run();
}[dependencies.engine]
path = "../engine"
default-features = false
features = ["physics", "fps"]
[dependencies]
bevy = { version = "=0.19.1", default-features = false }
[features]
# cargo run / cargo test keep every debug tool
default = ["dev"]
dev = ["engine/devtools", "engine/trace"]Your main.rs is the whole setup
engine::app builds the window, the app states, settings, menus and pause. Your plugin is the game. Ship builds drop every debug tool with --no-default-features.
Settings are data: each FieldDef becomes a row on the generated screen, saved to settings.json.
Drawn on a budget, with zero code
- meshes drawn
from 40,231to 480- 40k-prop city, overview
- ms p95 frame
from 4.54to 3.07- same view
- GPU busy in menus
from 85 to 92%to 21 to 32%- frame pacing
Apple M5 Pro, 1920×1080, High quality, vsync off. Before: every optimisation off. After: the defaults.


Near the camera nothing changes. Far props fade out in a dithered cross-fade, so nothing pops, and far blocks become flat copies. GPU occlusion culling was tried too and left off: it was 3 to 15% slower in every pose measured.
Read the render budgetEvery run, against the last one

--metrics-window

Press F4 in a dev build and a second window charts frame time, CPU, GPU and memory, then keeps a table of past runs tied to the git commit. Green is better, red is worse, dim is within 3%.
A control port and an MCP server let tests and AI agents drive the game: send keys, read state, take offscreen screenshots. Every engine capture on this site was taken that way.
- Ship buildsEnforced
--no-default-featuresremoves all of it. The ashlar debug crates the devtools are built on are not even in the dependency tree, and the packaging script refuses to package if one slipped in. - Clean exitsA pipelined-rendering deadlock on exit was found and fixed: clean exits went from 22 of 30 runs to 30 of 30.30 / 30



