agentleFS
chrisgliddonchrisgliddon/bevy-skills/skills/bevy-core-concepts/SKILL.md

bevy-core-concepts

Skill29 starsChanged 11 days agoMITView on GitHub

Use when wiring up an `App`, writing a `Plugin`, choosing between `Update` and `FixedUpdate`, ordering `Startup`/`PreUpdate`/`PostUpdate`, writing an exclusive system that takes `&mut World` as a `SystemParam`, or deciding between `.chain()` and `.chain_weak()` in Bevy 0.20. Covers the schedule graph, run order, `set_executor`, and ambiguity detection.

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Rule of thumb: if a system must produce the same result for the same inputs regardless of frame rate, put it in FixedUpdate.

    line 83
  2. Very specific:

    Don't put rendering systems in Update.

    line 117

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Bevy 0.20 — Core concepts (App, Plugin, Schedule, World)

When to use this skill

  • Setting up a new Bevy app or library plugin.
  • Deciding which schedule (Startup, Update, FixedUpdate, PostUpdate, ...) a system belongs in.
  • Writing an exclusive system that needs &mut World.
  • Hitting ScheduleBuildError panics at startup ("cycle", "hierarchy") or wanting ambiguity checks.
  • Asking "where should X run?"

Canonical pattern

use bevy::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(GamePlugin)
        .run();
}

pub struct GamePlugin;

impl Plugin for GamePlugin {
    fn build(&self, app: &mut App) {
        app.insert_resource(Score(0))
            .add_systems(Startup, spawn_world)
            .add_systems(Update, (read_input, apply_gravity).chain())
            .add_systems(FixedUpdate, simulate_physics)
            .add_systems(PostUpdate, sync_transforms)
            .add_systems(Last, rebuild_index);
    }
}

#[derive(Resource)]
struct Score(u32);

fn spawn_world(mut commands: Commands) {
    commands.spawn((Camera3d::default(), Transform::from_xyz(0.0, 4.0, 8.0)));
}

fn read_input(_keys: Res<ButtonInput<KeyCode>>) {}
fn apply_gravity(_t: Res<Time>) {}
fn simulate_physics(_t: Res<Time<Fixed>>) {}
fn sync_transforms(_q: Query<&mut Transform>) {}

// Exclusive system. Since 0.20 `&mut World` is an ordinary `SystemParam`:
// it can sit anywhere in the list, beside params that do not borrow the
// World (`Local`). The executor still runs it alone.
fn rebuild_index(mut runs: Local<u32>, world: &mut World) {
    *runs += 1;
    let count = world.entities().len();
    world.insert_resource(EntityCount(count));
}

#[derive(Resource)]
struct EntityCount(u32);

Schedule cheat sheet

ScheduleUse for
StartupOne-shot setup. Runs once before Update.
PreUpdateReading input, networking ingress, frame-start bookkeeping.
UpdatePer-frame game logic. Runs at render rate. Default for game systems.
PostUpdateReactive bookkeeping after Update (transform propagation, UI layout live here).
LastAnything that must run after PostUpdate. Window close/exit systems live here since 0.19.
FixedFirst / FixedPreUpdate / FixedUpdate / FixedPostUpdate / FixedLastDeterministic, tick-rate work: physics, networking simulation, gameplay state. Runs zero or more times per render frame to catch up to Time<Fixed>.

Rule of thumb: if a system must produce the same result for the same inputs regardless of frame rate, put it in FixedUpdate. Otherwise Update.

Ambiguity: it does not panic by default

Two unordered systems that write the same data run in an arbitrary order. ScheduleBuildSettings::default() sets ambiguity_detection: LogLevel::Ignore. Opt in to catch them:

fn randomize_update_order(app: &mut App) {
    use bevy::ecs::schedule::{LogLevel, ScheduleBuildSettings};
    app.edit_schedule(Update, |schedule| {
        schedule.set_build_settings(ScheduleBuildSettings {
            // Turn silent ambiguities into a build-time panic.
            ambiguity_detection: LogLevel::Error,
            ..default()
        });
    });
}

With the debug feature, ScheduleBuildSettings also has shuffle_seed: Option<u64> (0.20). Set it to shuffle unordered systems and prove your ordering constraints are complete.

Gotchas (0.18 → 0.20)

  • Resources are components (0.19). #[derive(Resource)] now implies Component; #[derive(Component, Resource)] on one type fails to compile. Res/ResMut/insert_resource are unchanged. Each resource lives on a hidden entity tagged IsResource, so broad queries (Query<Entity>, Query<EntityRef>) see it; add Without<IsResource> (bevy::ecs::resource::IsResource) when they conflict with ResMut.
  • Non-send resources are "non-send data" (0.19). init_non_send_resource → init_non_send, insert_non_send_resource → insert_non_send, World::non_send_resource → World::non_send. Old names are deprecated.
  • Exclusive systems are unified (0.20). ExclusiveSystemParam, ExclusiveFunctionSystem and System::is_exclusive() are gone. &mut World is a SystemParam and need not be the first argument. WorldId is no longer a valid exclusive param; use world.id() or Local<WorldId>.
  • SimpleExecutor was removed in 0.18. Insertion order no longer means run order. The default executor is multi-threaded and ambiguities are ignored, not reported, unless you set ambiguity_detection (see above). Stale LLM output claiming "ambiguity panics at startup" is wrong.
  • set_executor replaced ExecutorKind (0.19). schedule.set_executor_kind(ExecutorKind::SingleThreaded) → schedule.set_executor(SingleThreadedExecutor::new()). get_executor_kind has no replacement.
  • Built-in schedules order their sets with .chain_weak() (0.20). PostUpdate UI sets and the render-world sets only order systems whose tracked data access conflicts. If your system relied on a previous set finishing (via channels, atomics, interior mutability), add an explicit .after(system).
  • FallbackErrorHandler (0.19), panics become errors (0.20). DefaultErrorHandler is renamed. In 0.20 a panic in a system, command or observer is routed to the fallback handler, which re-panics by default; override it to log and continue.
  • ScheduleBuildError variants (0.18+). HierarchyLoop → HierarchySort(DiGraphToposortError::Loop(..)), DependencyCycle → DependencySort(DiGraphToposortError::Cycle(..)), Ambiguity(AmbiguousSystemConflictsWarning).
  • FunctionSystem<M, I, O, F> (0.18+) carries an In generic. Type aliases written as FunctionSystem<M, O, F> fail.
  • System::type_id → System::system_type (0.19) to stop shadowing Any::type_id.
  • NextState::set always transitions; set_if_different skips same-state (0.20 name, was set_if_neq). See bevy-ecs-systems.
  • Don't put rendering systems in Update. The render world is extracted automatically. See bevy-rendering.
  • Exclusive systems block everything in their schedule. Use them only when you really need &mut World (scene loading, batch reflection, schema migrations).

See also

  • bevy-ecs-systems — system params, sets, run conditions, ordering, chain_weak.
  • bevy-ecs-components — what entities are made of; observers and hooks.
  • bevy-migration-0-18-to-0-19 — resources-as-components, executor and error-handler renames.
  • bevy-migration-0-19-to-0-20 — exclusive-system unification, weak ordering, observer generics.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.