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,138 @@
# Bevy Skill Research: Events, Messages & Observers
> **Bevy version**: 0.18, with 0.19 notes
> **Date**: 2026-07-07
## The Event/Message Split (0.18 Breaking Change)
In 0.18, the old `Event`/`EventWriter`/`EventReader` system was **split into two distinct systems**:
| Concept | Trait | Writer | Reader | Model |
|---------|-------|--------|--------|-------|
| Messages | `#[derive(Message)]` | `MessageWriter<E>` | `MessageReader<E>` | Pull-based, double-buffered, batch |
| Events | `#[derive(Event)]` | `commands.trigger()` | Observers (`On<E>`) | Push-based, immediate |
```rust
// Messages — for batch processing, decoupled many-to-many
#[derive(Message)]
struct Collision { entity_a: Entity, entity_b: Entity }
// Events — for push-based observer reactions
#[derive(Event)]
struct Speak { message: String }
```
## Messages (Pull-Based, Buffered)
The renamed successor to the old EventWriter/EventReader pattern.
- `MessageWriter::write(msg)` / `write_batch(iter)` / `write_default()`
- `MessageReader::read()` returns iterator, advances cursor; `len()`, `is_empty()`, `clear()`
- Double-buffered: messages survive one `update()` cycle, cleared on the next
- Multiple readers can run concurrently; writers for the same type cannot
```rust
fn send(mut writer: MessageWriter<Collision>) {
writer.write(Collision { entity_a: a, entity_b: b });
}
fn receive(mut reader: MessageReader<Collision>) {
for collision in reader.read() {
// handle
}
}
```
## Events + Observers (Push-Based, Immediate)
**Observer system parameter**: `On<'w, 't, E, B>` where `B: Bundle` filters by component.
- `event()` / `event_mut()` — access event data
- `trigger()` / `trigger_mut()` — access trigger context
- `observer()` — the observer entity
- Implements `Deref<Target=E>` for direct field access
### Registration
```rust
// Global observer
app.add_observer(|on: On<Speak>| {
println!("{}", on.message);
});
// Entity-scoped observer
commands.entity(id).observe(|on: On<Hit>| {
// only fires for this specific entity
});
```
### Triggering
```rust
commands.trigger(Speak { message: "Hello!".into() });
world.trigger_ref(&mut event); // inspect after observers mutate
```
### EntityEvent (Entity-Targeted Events)
```rust
#[derive(EntityEvent)]
struct Hit {
#[event_target]
entity: Entity,
damage: f32,
}
```
Supports `#[entity_event(propagate)]` for automatic hierarchy traversal (event bubbles up through ChildOf chain).
## Lifecycle Events (Component Triggers)
Built-in events fired on component lifecycle changes:
| Event | Fires when | Order |
|-------|-----------|-------|
| `Add` | Component added to entity that lacked it | First |
| `Insert` | Any insert (add or replace) | After `Add` |
| `Replace` | Component removed regardless of replacement | Before `Remove` |
| `Remove` | Component removed from entity | After `Replace` |
| `Despawn` | Entity despawned (per component) | Last |
```rust
world.add_observer(|_: On<Add, Health>| { /* entity just got Health */ });
world.add_observer(|_: On<Remove, Health>| { /* Health was removed */ });
```
## Low-Level Component Hooks
Synchronous callbacks during the ECS operation itself (not scheduled systems). These run inline — not deferred.
```rust
#[derive(Component)]
#[component(on_add = my_hook)]
#[component(on_insert = my_hook)]
struct MyComponent;
fn my_hook(world: DeferredWorld, ctx: HookContext) { /* ... */ }
```
Available: `on_add`, `on_insert`, `on_replace`, `on_remove`, `on_despawn`.
## When to Use What
| Mechanism | Model | Best for |
|-----------|-------|----------|
| `MessageWriter`/`MessageReader` | Pull, scheduled, buffered | Batch processing, decoupled many-to-many communication |
| `Event` + Observers | Push, immediate | React NOW to something (structural changes, gameplay events) |
| Component hooks | Synchronous, inline | Deterministic callback AS PART OF the ECS operation |
| `Changed<T>` / `Added<T>` filters | Pull, per-tick polling | Check mutations each frame in a normal system |
**Rule of thumb**: Use Messages when multiple systems need to process the same data independently. Use Events + Observers when you need immediate reaction to something happening. Use hooks when the response must be atomic with the ECS operation.
## Migration Notes
**0.17 → 0.18**: `EventWriter``MessageWriter`, `EventReader``MessageReader`, `Events``Messages`. `Event` trait is now observer-only. `on_event` run condition → `on_message`.
**0.18 → 0.19**: `EntityComponentsTrigger` gains archetype fields — destructuring requires `..`:
```rust
let EntityComponentsTrigger { components, .. } = e.trigger(); // 0.19
```