Scaffold bevy plugin with bevy-0.19 and bevy-upgrade skills

Add plugin skeleton with versioned skill directories, TODO SKILL.md
scaffolds with goals for skill-creator, and copy over bevy-skill-research
reports from spacecraft into docs/workpad/research/.
This commit is contained in:
2026-07-07 17:52:00 +02:00
parent 8545738a05
commit 325e278af1
13 changed files with 1211 additions and 0 deletions
@@ -0,0 +1,94 @@
# Bevy Skill Research: Plugin Architecture
> **Bevy version**: 0.18, with 0.19 notes
> **Date**: 2026-07-07
## Plugin Trait (`bevy::app::Plugin`)
**Required**:
- `fn build(&self, app: &mut App)` — runs immediately on `add_plugins`
**Optional (provided defaults)**:
- `fn ready(&self, _app: &App) -> bool` — default `true`; override for async init
- `fn finish(&self, _app: &mut App)` — runs after all plugins report `ready()`, before first schedule tick; use for setup that depends on other plugins' async work
- `fn cleanup(&self, _app: &mut App)` — runs after `finish()`, before schedule; remove temp resources
- `fn name(&self) -> &str` — defaults to type name; used for uniqueness check
- `fn is_unique(&self) -> bool` — default `true`; set `false` for multi-instance plugins
Trait bounds: `Downcast + Any + Send + Sync`.
Any `fn(&mut App)` auto-implements `Plugin`.
## Plugin Dependencies
No formal "depends-on" declaration. Patterns:
- `app.is_plugin_added::<T>() -> bool` to guard or panic
- Sub-plugins: call `app.add_plugins(ChildPlugin)` inside `build()`. Bevy deduplicates by name (unique plugins panic on double-add)
- `finish()` is the idiomatic place for cross-plugin setup that needs another plugin's resources to exist
## Plugin Groups
`DefaultPlugins` / `MinimalPlugins` implement `PluginGroup`.
Customization via `PluginGroupBuilder` fluent API:
- `.disable::<T>()` — skip a plugin
- `.set(plugin)` — replace a plugin's config
- `.add_before::<Target>(plugin)` / `.add_after::<Target>(plugin)` — ordering
- `.enable::<T>()` — re-enable disabled
```rust
DefaultPlugins.disable::<AudioPlugin>().set(WindowPlugin { ... })
```
## App Builder Key Methods
| Method | Purpose |
|--------|---------|
| `add_plugins(P)` | Accepts plugin, plugin group, or tuple |
| `insert_resource(R)` | Insert (overwrites existing) |
| `init_resource::<R>()` | Insert via `Default`/`FromWorld` if absent |
| `add_systems(schedule, systems)` | Add to a named schedule |
| `configure_sets(schedule, sets)` | Configure system set ordering |
| `register_type::<T>()` | Register in `AppTypeRegistry` (reflection) |
| `is_plugin_added::<T>()` | Check if plugin was already added |
| `register_required_components::<T, R>()` | Component requirements (0.15+) |
## States (`bevy::state`)
Three flavors:
- **FreelyMutableState**: Changed via `NextState<S>` resource
- **SubStates**: Removed from world when parent state isn't in required variant
- **ComputedStates**: Derived automatically via `fn compute(sources) -> Option<Self>`
Registration:
- `app.init_state::<S>()` — register with default initial
- `app.insert_state(value)` — register with explicit initial
Transition schedules: `OnEnter(S)`, `OnExit(S)`, `OnTransition(S)`
Entity scoping: `DespawnOnEnter(S)`, `DespawnOnExit(S)` (replaced `StateScoped` in 0.16+)
Run condition: `in_state(S) -> impl Condition` (in `bevy::state::condition`)
**0.18 change**: `State::set()` now always triggers `OnEnter`/`OnExit` even for same-state transitions. Use `set_if_neq()` for old behavior.
## Schedules (Built-in Labels)
| Phase | Schedules |
|-------|-----------|
| Startup | `PreStartup`, `Startup`, `PostStartup` (run once) |
| Per-frame | `First`, `PreUpdate`, `Update`, `PostUpdate`, `Last` |
| Fixed timestep | `FixedFirst`, `FixedPreUpdate`, `FixedUpdate`, `FixedPostUpdate`, `FixedLast` (inside `FixedMain`, driven by `RunFixedMainLoop`) |
| Other | `Main`, `SpawnScene` (new) |
## Run Conditions (`bevy::ecs::schedule::common_conditions`)
Key functions:
- `in_state(s)` — state matching
- `resource_exists::<R>()`, `resource_added::<R>()`, `resource_changed::<R>()`, `resource_equals(val)`
- `any_with_component::<T>()`, `any_component_removed::<T>()`
- `run_once()`, `not(cond)`
- `on_message::<E>()` (renamed from `on_event` in 0.18)
- `condition_changed(cond)`, `condition_changed_to(cond)`
Combinators: `.and(cond)`, `.or(cond)` on any condition.