Bevy: lessons and gotchas¶
Written 2026-10-27 from the project's own docs.
Practical lessons recorded while building Fboard on Bevy 0.19. Most come from the project's handoff notes; the run-condition item was also checked against the bevy_ecs 0.19.1 source.
Bevy 0.19 renames¶
Older documentation, tutorials and examples use names that changed. The project's notes list the 0.19 names Fboard had to use; the "what it replaces" column is this page's own summary of earlier Bevy versions, not from the project's notes, so verify it against the migration guides if it matters.
| Bevy 0.19 (from the project's notes) | What it replaces (this page's summary) |
|---|---|
Message, MessageReader, MessageWriter |
Event, EventReader, EventWriter for buffered events between systems |
run_if(a.and_then(b)); .and is deprecated |
run_if(a.and(b)) |
DespawnOnExit(state) |
earlier state-scoped despawn helpers |
TransformSystems::Propagate |
the older transform-propagation system set name |
shadow_maps_enabled on lights |
the older shadow toggle field |
FontSize::Px |
a bare number for UI font size |
border_radius inside Node |
a separate border-radius component |
The .and / .and_then reversal
In the bevy_ecs 0.19.1 source, and is marked deprecated "since 0.19.0" with the note "use .and_then(...) instead, or .and_eager(...) to evaluate the conditions eagerly". This page's writer recalls earlier Bevy releases going the other way (deprecating and_then in favour of and); that recollection is unverified here. Either way, check the source of the version you are on.
The project's advice: keep a local checkout of the Bevy repository at the matching release tag, because its examples/ folder is the most reliable reference for the current API.
Build times¶
- The first build of Bevy took about 4.5 minutes on the project's machine; incremental builds take seconds.
- Use Bevy's recommended dev profile:
opt-level = 1for your own code andopt-level = 3for dependencies. In the words of Fboard's manifest: "our code builds fast, dependencies run fast."
Use Bevy as a dependency, not a fork¶
Fboard's architecture notes say nothing in the game needs engine changes. If that ever changes, the plan is to patch the one affected crate with Cargo's [patch.crates-io] rather than fork the engine. Staying on the published crates keeps upgrades to a version bump plus a migration guide.
Testing a game nobody has played¶
Fboard was built largely without a person at the controls, so it leans on making the game drive itself:
- Scripted smoke runs. An environment variable switches the client into a scripted scenario (a career run, a menu walk-through, a level tour) that saves screenshots and exits. The project rule is to look at every screenshot: a clean exit alone proves little.
- Screenshot timing. A screenshot captures the frame after that step's action. Put screenshots on steps whose action does nothing, or you capture the wrong moment.
- Isolate saves and sound. Point the game's data directory at a scratch folder and set master volume to 0 in its settings, so test runs don't touch real saves or play through the speakers. Smoke runs still open a window for several seconds.
- Frame-locked capture. To render a replay to video, the capture plugin sets Bevy's
TimeUpdateStrategyso each frame advances time by exactly one simulation tick, reads back each rendered frame with Bevy's screenshot API, and pipes it to ffmpeg. The video runs at real speed however slowly the capture runs. The soundtrack is rendered offline and muxed in afterwards, so the app must not quit before that step finishes. One of the project's exit options did, and the notes warn against combining them.
Determinism¶
- Fboard's replays record one input per tick and check a state hash every 300 ticks. A headless release build and the windowed dev client agree bit for bit on the same machine, and a one-tick change to an input is caught.
- That does not make the simulation safe for lockstep networking. It uses 32-bit floats and trigonometry, which aren't guaranteed bit-identical across CPUs and compilers. The networking plan is therefore an authoritative server with client-side prediction, not lockstep.
- What made determinism possible: the simulation is a pure function of (state, input), and input edges such as "button released" are derived inside the simulation from the previous tick's input rather than read from the engine.
Smaller notes¶
- Disabling Bevy's
AudioPlugindoesn't remove its audio backend from the build: thebevycrate still compilesrodiowith default features. Trimming it means turning off default features, which Fboard hasn't done. - Fboard stores Kira's audio manager as a non-send resource (inserted with
insert_non_send, read throughNonSendMut). The audio systems take it as anOption, and setup only logs a warning if no output device opens, so the game still runs without sound.