🤖 Flesh out bevy-0.19 and bevy-upgrade skills from research

Replace scaffold placeholders with full skill content:
- bevy-0.19: ECS cheat sheet, critical traps table, architecture guide, and 7 reference files
- bevy-upgrade: version migration workflow, transition references for 0.15→0.19, and 3 per-bump reference files
This commit is contained in:
2026-07-07 17:52:10 +02:00
parent 325e278af1
commit c661027d19
14 changed files with 927 additions and 53 deletions
+176 -28
View File
@@ -1,38 +1,186 @@
---
name: bevy-0.19
description: "TODO: Use the skill-creator skill to write this. See scaffolding goals below."
description: Write and navigate Bevy 0.19 (Rust game engine) code — ECS, plugins, systems, queries, events/messages, hierarchy, input, camera, rendering, assets. Use whenever editing Rust code that imports bevy, or the task involves Bevy components, systems, entity spawning, or game architecture. Pre-training knowledge of Bevy APIs is stale; consult this before writing Bevy code. For version migrations, defer to the bevy-upgrade skill.
---
# Bevy 0.19 — Implementation Helper
# Bevy 0.19
> **This skill is a scaffold.** Use `/skill-creator` to create the full skill content from the research material.
Bevy's API changes fundamentally between minor versions. Code that looks correct from memory frequently targets 0.140.17 APIs and either fails to compile or — worse — compiles and silently misbehaves. This skill is ground truth for 0.19.
## Scaffolding Goals
## Step 0: Confirm the version
1. **Use the `skill-creator:skill-creator` skill** to create the full SKILL.md content
2. Feed it the research reports from [`docs/workpad/research/bevy-skill-research/`](../../../../docs/workpad/research/bevy-skill-research/00-index.md)
3. Follow **Approach A** from the [design sketch](../../../../docs/workpad/research/bevy-skill-research/08-landscape-and-design-sketch.md): compact always-loaded cheat sheet (~500 lines) plus `references/` folder for deep dives
4. The skill should trigger on Bevy 0.19 Rust code (`bevy = "0.19"` in Cargo.toml, `use bevy::prelude::*`, `App::new()`, `#[derive(Component)]`, etc.)
5. Core content priorities (ranked by LLM error frequency):
- Event/Message rename and split (0.18)
- ChildOf replacing Parent (0.16+)
- Fallible systems with `-> Result` (0.18)
- Coordinate system gotchas
- Transform propagation timing
- Resources-as-Components (0.19)
6. Populate `references/` with per-topic deep-dive files derived from research reports 01-07
7. The skill should know about `bevy-upgrade` and invoke it when upgrade/migration work is detected
Check `Cargo.toml` for the `bevy` version before writing code.
## Reference Material
- **0.19** — this skill applies directly.
- **0.18** — most of this skill applies; 0.19-only notes are marked. Do not use 0.19-only APIs.
- **≤0.17, or the task is a version bump/migration** — use the **bevy-upgrade** skill. It owns rename tables and semantic shifts per version transition. Come back here for writing new idiomatic code once the target version is settled.
The research that feeds this skill lives in the workpad:
## Critical traps
- [00-index.md](../../../../docs/workpad/research/bevy-skill-research/00-index.md) — Research overview and design rationale
- [01-plugins.md](../../../../docs/workpad/research/bevy-skill-research/01-plugins.md) — Plugin lifecycle
- [02-ecs-core.md](../../../../docs/workpad/research/bevy-skill-research/02-ecs-core.md) — ECS fundamentals
- [03-hierarchy.md](../../../../docs/workpad/research/bevy-skill-research/03-hierarchy.md) — Hierarchy and ChildOf
- [04-events-observers.md](../../../../docs/workpad/research/bevy-skill-research/04-events-observers.md) — Events, messages, observers
- [05-input.md](../../../../docs/workpad/research/bevy-skill-research/05-input.md) — Input handling
- [06-camera-gotchas.md](../../../../docs/workpad/research/bevy-skill-research/06-camera-gotchas.md) — Camera and rendering
- [07-lessons-from-production.md](../../../../docs/workpad/research/bevy-skill-research/07-lessons-from-production.md) — Production gotchas
- [08-landscape-and-design-sketch.md](../../../../docs/workpad/research/bevy-skill-research/08-landscape-and-design-sketch.md) — Architecture options
The highest-frequency mistakes. When in doubt, trust this table over memory:
| Trap | Wrong (stale) | Correct (0.18/0.19) |
|------|---------------|---------------------|
| Parent component | `Parent` | `ChildOf(entity)` |
| Get parent | `*child_of` (deref) | `child_of.parent()` |
| Child iteration | `children.iter().copied()` | `for child in &children` |
| Recursive despawn | `despawn_recursive()` | `despawn()` (recursive by default) |
| Buffered events | `EventWriter`/`EventReader` | `MessageWriter`/`MessageReader` |
| Run condition | `on_event::<T>()` | `on_message::<T>()` |
| Bundles | `#[derive(Bundle)]` | spawn tuples + `#[require(...)]` |
| System errors | infallible `fn(...)` everywhere | `fn(...) -> Result` where fallible |
| Forward direction | +Z | **-Z** (`Transform::forward()`) |
| GLTF model forward | -Z like Bevy | **+Z** — negate or use `transform.back()` |
| `GlobalTransform` in `Update` | assumed current | stale — propagation runs in `PostUpdate` |
| Material shader bind group | `@group(2)` | `@group(3)` (group 2 is mesh bindings) |
| Same-state `set()` | no-op | fires `OnExit`+`OnEnter`; use `set_if_neq()` |
| `AmbientLight` | one global type | `GlobalAmbientLight` resource vs `AmbientLight` camera component |
| `ShaderRef` import | `bevy::render::render_resource` | `bevy::shader::ShaderRef` |
| `WinitPlugin` | `WinitPlugin::<WakeUp>` | `WinitPlugin` (not generic) |
**0.19-specific:**
| Trap | Detail |
|------|--------|
| Resources are components | Resources live on entities; `Query<Entity>` matches them — filter broad queries with `Without<IsResource>`. Cannot derive both `Component` and `Resource` on one type. |
| Scene renamed | `Scene`/`SceneRoot``WorldAsset`/`WorldAssetRoot` |
| `Ref<T>.clone()` | Returns `Ref<T>`, not the inner value — use `ref.deref().clone()` |
| `Assets::get_mut` | Returns `AssetMut<A>` (mutation-tracked), not `&mut A` |
| GLTF materials | Return `GltfMaterial`; use `#Material0/std` label for `StandardMaterial` |
## ECS core
### Components
```rust
#[derive(Component)]
struct Health(f32);
#[derive(Component)]
#[require(Transform, Visibility)] // auto-inserted when Player is added
struct Player;
```
- `#[require(B, C)]` replaces bundles: spawning the marker pulls in its dependencies. Constructors work: `#[require(Speed(5.0))]`, `#[require(Timer = default_timer())]`.
- `#[component(immutable)]` (0.18+) forbids `&mut T` access.
- `#[component(storage = "SparseSet")]` for frequently added/removed components.
- Lifecycle hooks: `#[component(on_add = f)]`, also `on_insert`, `on_replace`, `on_remove`, `on_despawn` — synchronous, inline with the ECS operation.
### Systems
Plain functions of `SystemParam`s. Make fallible systems return `Result` (0.18+) — errors log and the system retries next tick; `?` works with anything convertible to `BevyError`:
```rust
fn apply_damage(mut q: Query<&mut Health>, mut reader: MessageReader<Damage>) -> Result {
for msg in reader.read() {
q.get_mut(msg.target)?.0 -= msg.amount;
}
Ok(())
}
```
Useful params beyond the obvious: `Single<D, F>` (exactly one match or panic), `Populated<D, F>` (skip system if empty), `Option<Res<T>>` (resource may not exist — required for render resources in headless contexts), `Local<T>` (per-system persistent state), `RemovedComponents<T>`.
### Queries
- Data: `&T`, `&mut T`, `Entity`, `Has<T>`, tuples.
- Filters: `With<T>`, `Without<T>`, `Added<T>`, `Changed<T>`, `Spawned`, `Or<(A, B)>`.
- `.single()` panics unless exactly one match; prefer `Single` param or handle the `Result`.
- Derive `QueryData`/`QueryFilter` for reusable complex queries.
### Commands
`commands.spawn((CompA, CompB))` with tuples. `commands.entity(e).insert(..)` / `.insert_if_new(..)` / `.remove::<T>()` / `.despawn()`. Commands are deferred — they apply at the next sync point, not immediately.
### Ordering
`(a, b).chain()`, `.before(x)` / `.after(x)`, `#[derive(SystemSet)]` + `configure_sets`. Unordered systems with overlapping mutable access run in nondeterministic order — order anything where it matters.
## Hierarchy
`ChildOf(parent)` on the child is the source of truth; `Children` on the parent is auto-synced. Despawning a parent despawns descendants.
```rust
commands.spawn((Enemy, ChildOf(parent)));
commands.entity(parent).with_children(|s| { s.spawn(Turret); });
commands.entity(parent).add_child(existing);
```
Detaching without despawning: `detach_child` / `detach_children` / `detach_all_children` (0.18 names). Despawn only the children: `despawn_related::<Children>()`.
Details, custom relationships, transform propagation: `references/hierarchy.md`.
## Events vs Messages — pick correctly
0.18 split the old event system in two. Choosing wrong is the most common architectural error:
| Mechanism | Model | Use for |
|-----------|-------|---------|
| `#[derive(Message)]` + `MessageWriter`/`MessageReader` | Pull, double-buffered, batch | Decoupled many-to-many data flow; multiple systems consuming the same stream |
| `#[derive(Event)]` + `commands.trigger()` + observers (`On<E>`) | Push, immediate | React *now*: gameplay reactions, structural changes |
| `#[derive(EntityEvent)]` + `#[event_target]` | Push, per-entity, can `propagate` up `ChildOf` | Entity-targeted reactions (hit, clicked) |
| Component hooks | Synchronous, inline | Response must be atomic with the ECS operation |
| `Changed<T>`/`Added<T>` filters | Per-tick polling | Cheap frame-by-frame reaction in a normal system |
Full API, lifecycle events (`Add`/`Insert`/`Replace`/`Remove`/`Despawn`), observer registration: `references/events-observers.md`.
## Architecture
How to structure a Bevy project — and how to navigate an unfamiliar one:
- **One plugin per feature/domain** (`CombatPlugin`, `InventoryPlugin`, `UiPlugin`), each owning its components, systems, messages, and state. `main.rs` should be little more than `App::new().add_plugins(...)`.
- **Navigate by the plugin tree**: start at `App::new()`, follow `add_plugins` recursively; grep `add_systems` and `#[derive(SystemSet)]` to map execution order; grep `#[derive(Component)]`/`#[derive(Resource)]` per module to map data ownership.
- **Cross-plugin communication** goes through Messages or shared resources — not direct system coupling. A plugin's components are its internals; its messages are its API.
- **States** (`init_state`, `OnEnter`/`OnExit`, `in_state` run conditions) for game flow; `DespawnOnExit(state)` for scoped entities. Remember same-state `set()` re-fires transitions — one-time setup in `OnEnter` needs `set_if_neq` discipline from callers.
- **No formal plugin dependencies exist.** Guard with `is_plugin_added::<T>()`, or do cross-plugin setup in `Plugin::finish()` (runs after all plugins are ready).
- **Headless-safe systems**: anything that may run in tests or dedicated servers must take render resources as `Option<Res<...>>`.
- **Spawn from standard schedules.** Entities spawned from exclusive/non-standard schedules (UI passes, custom schedules) can be invisible to plugins that hook the normal cycle — set a flag there, spawn in `Update`.
Plugin trait lifecycle, plugin groups, states, run conditions: `references/plugins-and-app.md`.
## Coordinates and transforms
- Right-handed. **+Y up, -Z forward, +X right.** `Transform::forward()` is -Z.
- GLTF models face +Z visually — opposite Bevy. Orient with `look_to(-dir, Vec3::Y)` or treat `transform.back()` as the model's facing.
- Transform propagation runs in `PostUpdate`: reading `GlobalTransform` in `Update` gives last frame's value. Move the reader to `PostUpdate` after `TransformSystem::TransformPropagate`, or accept the one-frame lag deliberately.
## Silent-failure checklist
Bevy often succeeds silently when misconfigured. If something "doesn't show up" with no errors:
1. **Mesh has no normals** → invisible. Use `.with_computed_flat_normals()` on programmatic meshes.
2. **GLTF spawned by raw handle** → nothing. Spawn scene labels: `"model.glb#Scene0"`.
3. **Parent lacks `Visibility`/`InheritedVisibility`** → children invisible.
4. **Object beyond default far plane (1000 units)** → culled.
5. **Asset cache poisoning**: `load_with_settings` with stripped meshes caches per *path*; a later plain `load()` of the same path returns the stripped version. Never mix stripped and rendered loads of one asset in one process.
6. **Assets aren't ready after `load()`** — gate on `AssetEvent::LoadedWithDependencies` or `is_loaded_with_dependencies()`.
7. **Raw `ButtonInput<MouseButton>` fires through UI** — world click handlers need UI-consumption checks.
More production gotchas (physics, multiplayer/shared-world, custom relationships): `references/production-gotchas.md`.
## Code style
Write pragmatic Rust, not defensive Rust:
- Prefer `let Ok(x) = q.single() else { return; }` / `let Some(x) = ... else { return; }` over nested `if let` pyramids.
- Prefer fallible systems with `?` over `unwrap()` chains — a logged error beats a panic in a frame loop.
- Small, single-purpose components; marker components (`struct Player;`) are idiomatic and free.
- Don't hand-roll what the ECS gives you: relationship sync, recursive despawn, required components, run conditions.
## References
Load on demand — each is self-contained:
| File | Read when working on |
|------|----------------------|
| `references/plugins-and-app.md` | Plugin trait/lifecycle, plugin groups, App builder, states, schedules, run conditions |
| `references/ecs.md` | Component/resource/system/query/command details beyond the summary above |
| `references/hierarchy.md` | ChildOf, Children, spawning/detaching, relationships, transform propagation |
| `references/events-observers.md` | Message vs Event APIs, observers, lifecycle events, hooks |
| `references/input.md` | Keyboard, mouse, gamepad, touch |
| `references/camera-rendering.md` | Cameras, coordinates, meshes, materials, shaders, assets, lighting |
| `references/production-gotchas.md` | Silent failures, physics-plugin traps, headless/server architecture |
## Related skill
**bevy-upgrade** — version migration (0.15→…→0.19): rename tables, semantic shifts, step-by-step upgrade workflow. Use it whenever the task is moving code *between* versions rather than writing code *for* one. It may in turn send you back here for post-migration idiomatic cleanup.